From b81ba20598704b7445f5b71386a42c429298b9d1 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Tue, 7 Nov 2023 15:04:29 +0000 Subject: [PATCH] refactor[react-devtools-shared]: minor parsing improvements and modifications --- .../src/backend/renderer.js | 1 - .../src/devtools/store.js | 119 ++++++++++++------ .../src/devtools/views/Components/Element.js | 9 +- .../devtools/views/Components/HocBadges.js | 11 +- .../views/Profiler/CommitTreeBuilder.js | 8 +- packages/react-devtools-shared/src/utils.js | 23 ++-- 6 files changed, 106 insertions(+), 65 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index 60581b4033fde..79260e9870e81 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -439,7 +439,6 @@ export function getInternalReactConstants(version: string): { return 'Cache'; case ClassComponent: case IncompleteClassComponent: - return getDisplayName(resolvedType); case FunctionComponent: case IndeterminateComponent: return getDisplayName(resolvedType); diff --git a/packages/react-devtools-shared/src/devtools/store.js b/packages/react-devtools-shared/src/devtools/store.js index a04a31cf630f7..5ae3d3cdc4fd2 100644 --- a/packages/react-devtools-shared/src/devtools/store.js +++ b/packages/react-devtools-shared/src/devtools/store.js @@ -27,7 +27,7 @@ import { setSavedComponentFilters, separateDisplayNameAndHOCs, shallowDiffers, - utfDecodeString, + utfDecodeStringWithRanges, } from '../utils'; import {localStorageGetItem, localStorageSetItem} from '../storage'; import {__DEBUG__} from '../constants'; @@ -450,7 +450,7 @@ export default class Store extends EventEmitter<{ } // This build of DevTools supports the legacy profiler. - // This is a static flag, controled by the Store config. + // This is a static flag, controlled by the Store config. get supportsProfiling(): boolean { return this._supportsProfiling; } @@ -467,7 +467,7 @@ export default class Store extends EventEmitter<{ } // This build of DevTools supports the Timeline profiler. - // This is a static flag, controled by the Store config. + // This is a static flag, controlled by the Store config. get supportsTimeline(): boolean { return this._supportsTimeline; } @@ -502,30 +502,58 @@ export default class Store extends EventEmitter<{ } // Find which root this element is in... - let rootID; let root; let rootWeight = 0; for (let i = 0; i < this._roots.length; i++) { - rootID = this._roots[i]; - root = ((this._idToElement.get(rootID): any): Element); + const rootID = this._roots[i]; + root = this._idToElement.get(rootID); + + if (root === undefined) { + this._throwAndEmitError( + Error( + `Couldn't find root with id "${rootID}": no matching node was found in the Store.`, + ), + ); + + return null; + } + if (root.children.length === 0) { continue; - } else if (rootWeight + root.weight > index) { + } + + if (rootWeight + root.weight > index) { break; } else { rootWeight += root.weight; } } + if (root === undefined) { + return null; + } + // Find the element in the tree using the weight of each node... // Skip over the root itself, because roots aren't visible in the Elements tree. - let currentElement = ((root: any): Element); + let currentElement: Element = root; let currentWeight = rootWeight - 1; + while (index !== currentWeight) { const numChildren = currentElement.children.length; for (let i = 0; i < numChildren; i++) { const childID = currentElement.children[i]; - const child = ((this._idToElement.get(childID): any): Element); + const child = this._idToElement.get(childID); + + if (child === undefined) { + this._throwAndEmitError( + Error( + `Couldn't child element with id "${childID}": no matching node was found in the Store.`, + ), + ); + + return null; + } + const childWeight = child.isCollapsed ? 1 : child.weight; if (index <= currentWeight + childWeight) { @@ -538,7 +566,7 @@ export default class Store extends EventEmitter<{ } } - return ((currentElement: any): Element) || null; + return currentElement || null; } getElementIDAtIndex(index: number): number | null { @@ -560,32 +588,31 @@ export default class Store extends EventEmitter<{ getElementsWithErrorsAndWarnings(): Array<{id: number, index: number}> { if (this._cachedErrorAndWarningTuples !== null) { return this._cachedErrorAndWarningTuples; - } else { - const errorAndWarningTuples: ErrorAndWarningTuples = []; - - this._errorsAndWarnings.forEach((_, id) => { - const index = this.getIndexOfElementID(id); - if (index !== null) { - let low = 0; - let high = errorAndWarningTuples.length; - while (low < high) { - const mid = (low + high) >> 1; - if (errorAndWarningTuples[mid].index > index) { - high = mid; - } else { - low = mid + 1; - } - } + } - errorAndWarningTuples.splice(low, 0, {id, index}); + const errorAndWarningTuples: ErrorAndWarningTuples = []; + + this._errorsAndWarnings.forEach((_, id) => { + const index = this.getIndexOfElementID(id); + if (index !== null) { + let low = 0; + let high = errorAndWarningTuples.length; + while (low < high) { + const mid = (low + high) >> 1; + if (errorAndWarningTuples[mid].index > index) { + high = mid; + } else { + low = mid + 1; + } } - }); - // Cache for later (at least until the tree changes again). - this._cachedErrorAndWarningTuples = errorAndWarningTuples; + errorAndWarningTuples.splice(low, 0, {id, index}); + } + }); - return errorAndWarningTuples; - } + // Cache for later (at least until the tree changes again). + this._cachedErrorAndWarningTuples = errorAndWarningTuples; + return errorAndWarningTuples; } getErrorAndWarningCountForElementID(id: number): { @@ -923,7 +950,11 @@ export default class Store extends EventEmitter<{ const nextLength = operations[i]; i++; - const nextString = utfDecodeString(operations.slice(i, i + nextLength)); + const nextString = utfDecodeStringWithRanges( + operations, + i, + i + nextLength - 1, + ); stringTable.push(nextString); i += nextLength; } @@ -1035,7 +1066,7 @@ export default class Store extends EventEmitter<{ ), ); - continue; + break; } parentElement.children.push(id); @@ -1088,7 +1119,7 @@ export default class Store extends EventEmitter<{ ), ); - continue; + break; } i += 1; @@ -1126,7 +1157,7 @@ export default class Store extends EventEmitter<{ ), ); - continue; + break; } const index = parentElement.children.indexOf(id); @@ -1172,7 +1203,17 @@ export default class Store extends EventEmitter<{ } }; - const root = ((this._idToElement.get(id): any): Element); + const root = this._idToElement.get(id); + if (root === undefined) { + this._throwAndEmitError( + Error( + `Cannot remove root "${id}": no matching node was found in the Store.`, + ), + ); + + break; + } + recursivelyDeleteElements(id); this._rootIDToCapabilities.delete(id); @@ -1194,7 +1235,7 @@ export default class Store extends EventEmitter<{ ), ); - continue; + break; } const children = element.children; @@ -1279,7 +1320,7 @@ export default class Store extends EventEmitter<{ this._revision++; - // Any time the tree changes (e.g. elements added, removed, or reordered) cached inidices may be invalid. + // Any time the tree changes (e.g. elements added, removed, or reordered) cached indices may be invalid. this._cachedErrorAndWarningTuples = null; if (haveErrorsOrWarningsChanged) { diff --git a/packages/react-devtools-shared/src/devtools/views/Components/Element.js b/packages/react-devtools-shared/src/devtools/views/Components/Element.js index b5486cd7ed83c..a48ebf76650f6 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/Element.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/Element.js @@ -122,7 +122,7 @@ export default function Element({data, index, style}: Props): React.Node { isStrictModeNonCompliant, key, type, - } = ((element: any): ElementType); + } = element; // Only show strict mode non-compliance badges for top level elements. // Showing an inline badge for every element in the tree would be noisy. @@ -173,17 +173,16 @@ export default function Element({data, index, style}: Props): React.Node { " )} + {hocDisplayNames !== null && hocDisplayNames.length > 0 ? ( - + ) : null} + {showInlineWarningsAndErrors && errorCount > 0 && ( - {hocDisplayNames !== null && - hocDisplayNames.map(hocDisplayName => ( -
- {hocDisplayName} -
- ))} + {hocDisplayNames.map(hocDisplayName => ( +
+ {hocDisplayName} +
+ ))} ); } diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js index 5b3b73482e3f4..ecb13dec8b12b 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js @@ -17,7 +17,7 @@ import { TREE_OPERATION_UPDATE_TREE_BASE_DURATION, TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS, } from 'react-devtools-shared/src/constants'; -import {utfDecodeString} from 'react-devtools-shared/src/utils'; +import {utfDecodeStringWithRanges} from 'react-devtools-shared/src/utils'; import {ElementTypeRoot} from 'react-devtools-shared/src/frontend/types'; import ProfilerStore from 'react-devtools-shared/src/devtools/ProfilerStore'; @@ -170,8 +170,10 @@ function updateTree( const stringTableEnd = i + stringTableSize; while (i < stringTableEnd) { const nextLength = operations[i++]; - const nextString = utfDecodeString( - (operations.slice(i, i + nextLength): any), + const nextString = utfDecodeStringWithRanges( + operations, + i, + i + nextLength - 1, ); stringTable.push(nextString); i += nextLength; diff --git a/packages/react-devtools-shared/src/utils.js b/packages/react-devtools-shared/src/utils.js index 924b0e8aef7aa..52de92b5d6489 100644 --- a/packages/react-devtools-shared/src/utils.js +++ b/packages/react-devtools-shared/src/utils.js @@ -116,7 +116,7 @@ export function getWrappedDisplayName( wrapperName: string, fallbackName?: string, ): string { - const displayName = (outerType: any).displayName; + const displayName = (outerType: any)?.displayName; return ( displayName || `${wrapperName}(${getDisplayName(innerType, fallbackName)})` ); @@ -152,15 +152,14 @@ export function getUID(): number { return ++uidCounter; } -export function utfDecodeString(array: Array): string { - // Avoid spreading the array (e.g. String.fromCodePoint(...array)) - // Functions arguments are first placed on the stack before the function is called - // which throws a RangeError for large arrays. - // See github.com/facebook/react/issues/22293 +export function utfDecodeStringWithRanges( + array: Array, + left: number, + right: number, +): string { let string = ''; - for (let i = 0; i < array.length; i++) { - const char = array[i]; - string += String.fromCodePoint(char); + for (let i = left; i <= right; i++) { + string += String.fromCodePoint(array[i]); } return string; } @@ -216,8 +215,10 @@ export function printOperationsArray(operations: Array) { const stringTableEnd = i + stringTableSize; while (i < stringTableEnd) { const nextLength = operations[i++]; - const nextString = utfDecodeString( - (operations.slice(i, i + nextLength): any), + const nextString = utfDecodeStringWithRanges( + operations, + i, + i + nextLength - 1, ); stringTable.push(nextString); i += nextLength;