Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save creationix/299390 to your computer and use it in GitHub Desktop.
Save creationix/299390 to your computer and use it in GitHub Desktop.
From 1734c81a20a2b9a8dcc58f5378f98a29aedb5eff Mon Sep 17 00:00:00 2001
From: Tim Caswell <tim@creationix.com>
Date: Tue, 9 Feb 2010 10:50:05 -0600
Subject: [PATCH] Rewrite sys.inspect to be more reliable and handle crazy edge cases.
---
lib/sys.js | 168 +++++++++++++++++++++++++--------------------
test/mjsunit/test-sys.js | 41 ++++++++++-
2 files changed, 131 insertions(+), 78 deletions(-)
diff --git a/lib/sys.js b/lib/sys.js
index 18b6359..c2489ad 100644
--- a/lib/sys.js
+++ b/lib/sys.js
@@ -21,9 +21,99 @@ exports.error = function (x) {
* in the best way possible given the different types.
*
* @param {Object} value The object to print out
+ * @param {Boolean} showHidden Flag that shows hidden (not enumerable) properties of objects.
*/
-exports.inspect = function (value) {
- return formatter(value, '', []);
+exports.inspect = function (obj, showHidden) {
+ var seen = [];
+ function format(value) {
+ // Primitive types cannot have properties
+ switch (typeof value) {
+ case 'undefined': return 'undefined';
+ case 'string': return JSON.stringify(value);
+ case 'number': return '' + value;
+ case 'boolean': return '' + value;
+ }
+ // For some reason typeof null is "object", so special case here.
+ if (value === null) {
+ return 'null';
+ }
+
+ // Look up the keys of the object.
+ var keys = showHidden ? Object.getOwnPropertyNames(value).map(function (key) {
+ return '' + key;
+ }) : Object.keys(value);
+ var visible_keys = Object.keys(value);
+
+ // Functions without properties can be shortcutted.
+ if (typeof value === 'function' && keys.length === 0) {
+ if (value instanceof RegExp) {
+ return '' + value;
+ } else {
+ return '[Function]';
+ }
+ }
+
+ var base, type, braces;
+ // Determine the object type
+ if (value instanceof Array) {
+ type = 'Array';
+ braces = ["[", "]"];
+ } else {
+ type = 'Object';
+ braces = ["{", "}"];
+ }
+
+ // Make functions say that they are functions
+ if (typeof value === 'function') {
+ base = (value instanceof RegExp) ? ' ' + value : ' [Function]';
+ } else {
+ base = "";
+ }
+
+ seen.push(value);
+
+ if (keys.length === 0) {
+ return braces[0] + base + braces[1];
+ }
+
+ return braces[0] + base + "\n" + (keys.map(function (key) {
+ var name, str;
+ if (value.__lookupGetter__) {
+ if (value.__lookupGetter__(key)) {
+ if (value.__lookupSetter__(key)) {
+ str = "[Getter/Setter]";
+ } else {
+ str = "[Getter]";
+ }
+ } else {
+ if (value.__lookupSetter__(key)) {
+ str = "[Setter]";
+ }
+ }
+ }
+ if (visible_keys.indexOf(key) < 0) {
+ name = "[" + key + "]";
+ }
+ if (!str) {
+ if (seen.indexOf(value[key]) < 0) {
+ str = format(value[key]);
+ } else {
+ str = '[Circular]';
+ }
+ }
+ if (typeof name === 'undefined') {
+ if (type === 'Array' && key.match(/^\d+$/)) {
+ return str;
+ }
+ name = JSON.stringify('' + key);
+ }
+
+ return name + ": " + str;
+ }).join(",\n")).split("\n").map(function (line) {
+ return ' ' + line;
+ }).join('\n') + "\n" + braces[1];
+ }
+ return format(obj);
};
exports.p = function (x) {
@@ -70,76 +160,4 @@ exports.exec = function (command) {
*/
exports.inherits = process.inherits;
-/**
- * A recursive function to format an object - used by inspect.
- *
- * @param {Object} value
- * the value to format
- * @param {String} indent
- * the indent level of any nested objects, since they are formatted over
- * more than one line
- * @param {Array} parents
- * contains all objects above the current one in the heirachy, used to
- * prevent getting stuck in a loop on circular references
- */
-var formatter = function(value, indent, parents) {
- switch(typeof(value)) {
- case 'string': return JSON.stringify(value);
- case 'number': return '' + value;
- case 'function': return '[Function]';
- case 'boolean': return '' + value;
- case 'undefined': return 'undefined';
- case 'object':
- if (value == null) return 'null';
- if (parents.indexOf(value) >= 0) return '[Circular]';
- parents.push(value);
-
- if (value instanceof Array && Object.keys(value).length === value.length) {
- return formatObject(value, indent, parents, '[]', function(x, f) {
- return f(value[x]);
- });
- } else {
- return formatObject(value, indent, parents, '{}', function(x, f) {
- var child;
- if (value.__lookupGetter__(x)) {
- if (value.__lookupSetter__(x)) {
- child = "[Getter/Setter]";
- } else {
- child = "[Getter]";
- }
- } else {
- if (value.__lookupSetter__(x)) {
- child = "[Setter]";
- } else {
- child = f(value[x]);
- }
- }
- return f(x) + ': ' + child;
- });
- }
- return buffer;
- default:
- throw('inspect unimplemented for ' + typeof(value));
- }
-}
-
-/**
- * Helper function for formatting either an array or an object, used internally by formatter
- */
-var formatObject = function(obj, indent, parents, parenthesis, entryFormatter) {
- var buffer = parenthesis[0];
- var values = [];
- var x;
-
- var localFormatter = function(value) {
- return formatter(value, indent + ' ', parents);
- };
- for (x in obj) {
- values.push(indent + ' ' + entryFormatter(x, localFormatter));
- }
- if (values.length > 0) {
- buffer += "\n" + values.join(",\n") + "\n" + indent;
- }
- buffer += parenthesis[1];
- return buffer;
-}
+// Object.create(null, {name: {value: "Tim", enumerable: true}})
\ No newline at end of file
diff --git a/test/mjsunit/test-sys.js b/test/mjsunit/test-sys.js
index 005834c..6ba2157 100644
--- a/test/mjsunit/test-sys.js
+++ b/test/mjsunit/test-sys.js
@@ -9,6 +9,7 @@ assert.equal('"hello"', inspect("hello"));
assert.equal("[Function]", inspect(function() {}));
assert.equal('undefined', inspect(undefined));
assert.equal('null', inspect(null));
+assert.equal('/foo(bar\\n)?/gi', inspect(/foo(bar\n)?/gi));
assert.equal("\"\\n\\u0001\"", inspect("\n\u0001"));
@@ -23,6 +24,24 @@ assert.equal('{\n "a": [Function]\n}', inspect({a: function() {}}));
assert.equal('{\n "a": 1,\n "b": 2\n}', inspect({a: 1, b: 2}));
assert.equal('{\n "a": {}\n}', inspect({'a': {}}));
assert.equal('{\n "a": {\n "b": 2\n }\n}', inspect({'a': {'b': 2}}));
+assert.equal('[\n 1,\n 2,\n 3,\n [length]: 3\n]', inspect([1,2,3], true));
+assert.equal("{\n \"visible\": 1\n}",
+ inspect(Object.create({}, {visible:{value:1,enumerable:true},hidden:{value:2}}))
+);
+assert.equal("{\n [hidden]: 2,\n \"visible\": 1\n}",
+ inspect(Object.create({}, {visible:{value:1,enumerable:true},hidden:{value:2}}), true)
+);
+
+// Objects without prototype
+assert.equal(
+ "{\n [hidden]: \"secret\",\n \"name\": \"Tim\"\n}",
+ inspect(Object.create(null, {name: {value: "Tim", enumerable: true}, hidden: {value: "secret"}}), true)
+);
+assert.equal(
+ "{\n \"name\": \"Tim\"\n}",
+ inspect(Object.create(null, {name: {value: "Tim", enumerable: true}, hidden: {value: "secret"}}))
+);
+
// Dynamic properties
assert.equal(
@@ -35,12 +54,28 @@ value['a'] = value;
assert.equal('{\n "a": [Circular]\n}', inspect(value));
value = Object.create([]);
value.push(1);
-assert.equal('{\n "0": 1,\n "length": 1\n}', inspect(value));
+assert.equal("[\n 1,\n \"length\": 1\n]", inspect(value));
// Array with dynamic properties
value = [1,2,3];
value.__defineGetter__('growingLength', function () { this.push(true); return this.length; });
assert.equal(
- "{\n \"0\": 1,\n \"1\": 2,\n \"2\": 3,\n \"growingLength\": [Getter]\n}",
+ "[\n 1,\n 2,\n 3,\n \"growingLength\": [Getter]\n]",
inspect(value)
-);
\ No newline at end of file
+);
+
+// Function with properties
+value = function () {};
+value.aprop = 42;
+assert.equal(
+ "{ [Function]\n \"aprop\": 42\n}",
+ inspect(value)
+);
+
+// Regular expressions with properties
+value = /123/ig;
+value.aprop = 42;
+assert.equal(
+ "{ /123/gi\n \"aprop\": 42\n}",
+ inspect(value)
+);
--
1.6.5.2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment