-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Support Iterators/Iterables, Immutable, other inspector improvements #459
Changes from all commits
ec28b8f
d66d750
6c8b5ac
e65a675
56f522f
033be6e
eaea710
2c18742
27f58a2
f3c5fe4
5d56a17
af638f1
1c276ae
ba8f87f
da50d3f
8d9d8dc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,61 @@ | |
| */ | ||
| 'use strict'; | ||
|
|
||
| /** | ||
| * Get a enhanced/artificial type string based on the object instance | ||
| */ | ||
| function getPropType(data: Object): string | null { | ||
| if (!data) { | ||
| return null; | ||
| } | ||
| var type = typeof data; | ||
|
|
||
| if (type === 'object') { | ||
| if (data._reactFragment) { | ||
| return 'react_fragment'; | ||
| } | ||
| if (Array.isArray(data)) { | ||
| return 'array'; | ||
| } | ||
| if (ArrayBuffer.isView(data)) { | ||
| if (data instanceof DataView) { | ||
| return 'data_view'; | ||
| } | ||
| return 'typed_array'; | ||
| } | ||
| if (data instanceof ArrayBuffer) { | ||
| return 'array_buffer'; | ||
| } | ||
| if (data[Symbol.iterator] === 'function') { | ||
| return 'iterator'; | ||
| } | ||
| } | ||
|
|
||
| return type; | ||
| } | ||
|
|
||
| /** | ||
| * Generate the dehydrated metadata for complex object instances | ||
| */ | ||
| function createDehydrated(type: string, data: Object, cleaned: Array<Array<string>>, path: Array<string>): Object { | ||
| var meta = {}; | ||
|
|
||
| if (type === 'array' || type === 'typed_array') { | ||
| meta.length = data.length; | ||
| } | ||
| if (type === 'iterator' || type === 'typed_array') { | ||
| meta.readOnly = true; | ||
| } | ||
|
|
||
| cleaned.push(path); | ||
|
|
||
| return { | ||
| type, | ||
| meta, | ||
| name: !data.constructor || data.constructor.name === 'Object' ? '' : data.constructor.name, | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Strip out complex data (instances, functions, and data nested > 2 levels | ||
| * deep). The paths of the stripped out objects are appended to the `cleaned` | ||
|
|
@@ -30,63 +85,72 @@ | |
| * } | ||
| * and cleaned = [["some", "attr"], ["other"]] | ||
| */ | ||
| function dehydrate(data: Object, cleaned: Array<Array<string>>, path?: Array<string>, level?: number): string | Object { | ||
| level = level || 0; | ||
| path = path || []; | ||
| if (typeof data === 'function') { | ||
| cleaned.push(path); | ||
| return { | ||
| name: data.name, | ||
| type: 'function', | ||
| }; | ||
| } | ||
| if (!data || typeof data !== 'object') { | ||
| if (typeof data === 'string' && data.length > 500) { | ||
| return data.slice(0, 500) + '...'; | ||
| } | ||
| function dehydrate(data: Object, cleaned: Array<Array<string>>, path?: Array<string> = [], level?: number = 0): string | Object { | ||
|
|
||
| var type = getPropType(data); | ||
|
|
||
| switch (type) { | ||
|
|
||
| case 'function': | ||
| cleaned.push(path); | ||
| return { | ||
| name: data.name, | ||
| type: 'function', | ||
| }; | ||
|
|
||
| case 'string': | ||
| return data.length <= 500 ? data : data.slice(0, 500) + '...'; | ||
|
|
||
| // We have to do this assignment b/c Flow doesn't think "symbol" is | ||
| // something typeof would return. Error 'unexpected predicate "symbol"' | ||
| var type = typeof data; | ||
| if (type === 'symbol') { | ||
| case 'symbol': | ||
| cleaned.push(path); | ||
| return { | ||
| type: 'symbol', | ||
| name: data.toString(), | ||
| }; | ||
| } | ||
| return data; | ||
| } | ||
| if (data._reactFragment) { | ||
|
|
||
| // React Fragments error if you try to inspect them. | ||
| return 'A react fragment'; | ||
| } | ||
| if (level > 2) { | ||
| cleaned.push(path); | ||
| return { | ||
| type: Array.isArray(data) ? 'array' : 'object', | ||
| name: !data.constructor || data.constructor.name === 'Object' ? '' : data.constructor.name, | ||
| meta: Array.isArray(data) ? { | ||
| length: data.length, | ||
| } : null, | ||
| }; | ||
| } | ||
| if (Array.isArray(data)) { | ||
| // $FlowFixMe path is not undefined. | ||
| return data.map((item, i) => dehydrate(item, cleaned, path.concat([i]), level + 1)); | ||
| } | ||
| // TODO when this is in the iframe window, we can just use Object | ||
| if (data.constructor && typeof data.constructor === 'function' && data.constructor.name !== 'Object') { | ||
| cleaned.push(path); | ||
| return { | ||
| name: data.constructor.name, | ||
| type: 'object', | ||
| }; | ||
| } | ||
| var res = {}; | ||
| for (var name in data) { | ||
| res[name] = dehydrate(data[name], cleaned, path.concat([name]), level + 1); | ||
| case 'react_fragment': | ||
| return 'A React Fragment'; | ||
|
|
||
| // ArrayBuffers error if you try to inspect them. | ||
| case 'array_buffer': | ||
| case 'data_view': | ||
| cleaned.push(path); | ||
| return { | ||
| type, | ||
| name: type === 'data_view' ? 'DataView' : 'ArrayBuffer', | ||
| meta: { | ||
| length: data.byteLength, | ||
| uninspectable: true, | ||
| }, | ||
| }; | ||
|
|
||
| case 'array': | ||
| if (level > 2) { | ||
| return createDehydrated(type, data, cleaned, path); | ||
| } | ||
| return data.map((item, i) => dehydrate(item, cleaned, path.concat([i]), level + 1)); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this would also be nice to limit to e.g. 100, but I'm fine w/ just putting a TODO
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding a fixed limit seems like a reasonable (but temporary) compromise. I think the more ideal solution would be to add an optional I'm happy to update the PR with a hardcoded limit and add a TODO |
||
|
|
||
| case 'typed_array': | ||
| case 'iterator': | ||
| return createDehydrated(type, data, cleaned, path); | ||
|
|
||
| case 'object': | ||
| if (level > 2 || (data.constructor && typeof data.constructor === 'function' && data.constructor.name !== 'Object')) { | ||
| return createDehydrated(type, data, cleaned, path); | ||
| } else { | ||
| var res = {}; | ||
| for (var name in data) { | ||
| res[name] = dehydrate(data[name], cleaned, path.concat([name]), level + 1); | ||
| } | ||
| return res; | ||
| } | ||
|
|
||
| default: | ||
| return data; | ||
| } | ||
| return res; | ||
| } | ||
|
|
||
| module.exports = dehydrate; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| /** | ||
| * Copyright (c) 2015-present, Facebook, Inc. | ||
| * All rights reserved. | ||
| * | ||
| * This source code is licensed under the BSD-style license found in the | ||
| * LICENSE file in the root directory of this source tree. An additional grant | ||
| * of patent rights can be found in the PATENTS file in the same directory. | ||
| * | ||
| */ | ||
|
|
||
| /** | ||
| * Retrieves the value from the path of nested objects | ||
| * @param {Object} base Base or root object for path | ||
| * @param {Array<String>} path nested path | ||
| * @return {any} Value at end of path or `mull` | ||
| */ | ||
| function getIn(base, path) { | ||
| return path.reduce((obj, attr) => { | ||
| if (obj) { | ||
| if (obj.hasOwnProperty(attr)) { | ||
| return obj[attr]; | ||
| } | ||
| if (typeof obj[Symbol.iterator] === 'function') { | ||
| // Convert iterable to array and return array[index] | ||
| return [...obj][attr]; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
see Improvement of Keyed iterables/iterators in this comment I hope I'm making sense here. |
||
| } | ||
| } | ||
|
|
||
| return null; | ||
| }, base); | ||
| } | ||
|
|
||
| module.exports = getIn; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this will get fairly unhappy for long lists. Can we bail after ~100 and indicate that there's more we're not showing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this can probably be done as a separate diff, I just would rather an extension update not make people's apps suddenly really slow under debugging