- /**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
- import JSON5 from 'json5'; 
- import type {Element} from 'react-devtools-shared/src/frontend/types'; 
- import type {StateContext} from './views/Components/TreeContext'; 
- import type Store from './store'; 
- export function printElement( 
- element: Element, 
- includeWeight: boolean = false, 
- ): string { 
- let prefix = ' '; 
- if (element.children.length > 0) { 
- prefix = element.isCollapsed ? '▸' : '▾'; 
- }
- let key = ''; 
- if (element.key !== null) { 
- key = ` key="${element.key}"`; 
- }
- let hocDisplayNames = null; 
- if (element.hocDisplayNames !== null) { 
- hocDisplayNames = [...element.hocDisplayNames]; 
- }
- const hocs = 
- hocDisplayNames === null ? '' : ` [${hocDisplayNames.join('][')}]`; 
- let suffix = ''; 
- if (includeWeight) { 
- suffix = ` (${element.isCollapsed ? 1 : element.weight})`; 
- }
- return `${' '.repeat(element.depth + 1)}${prefix} <${ 
- element.displayName || 'null' 
- }${key}>${hocs}${suffix}`; 
- }
- export function printOwnersList( 
- elements: Array<Element>, 
- includeWeight: boolean = false, 
- ): string { 
- return elements 
- .map(element => printElement(element, includeWeight)) 
- .join('\n'); 
- }
- export function printStore( 
- store: Store, 
- includeWeight: boolean = false, 
- state: StateContext | null = null, 
- ): string { 
- const snapshotLines = []; 
- let rootWeight = 0; 
- function printSelectedMarker(index: number): string { 
- if (state === null) { 
- return ''; 
- }
- return state.selectedElementIndex === index ? `→` : ' '; 
- }
- function printErrorsAndWarnings(element: Element): string { 
- const {errorCount, warningCount} = 
- store.getErrorAndWarningCountForElementID(element.id); 
- if (errorCount === 0 && warningCount === 0) { 
- return ''; 
- }
- return ` ${errorCount > 0 ? '✕' : ''}${warningCount > 0 ? '⚠' : ''}`; 
- }
- const ownerFlatTree = state !== null ? state.ownerFlatTree : null; 
- if (ownerFlatTree !== null) { 
- snapshotLines.push( 
- '[owners]' + (includeWeight ? ` (${ownerFlatTree.length})` : ''), 
- );
- ownerFlatTree.forEach((element, index) => { 
- const printedSelectedMarker = printSelectedMarker(index); 
- const printedElement = printElement(element, false); 
- const printedErrorsAndWarnings = printErrorsAndWarnings(element); 
- snapshotLines.push( 
- `${printedSelectedMarker}${printedElement}${printedErrorsAndWarnings}`, 
- );
- });
- } else { 
- const errorsAndWarnings = store._errorsAndWarnings; 
- if (errorsAndWarnings.size > 0) { 
- let errorCount = 0; 
- let warningCount = 0; 
- errorsAndWarnings.forEach(entry => { 
- errorCount += entry.errorCount; 
- warningCount += entry.warningCount; 
- });
- snapshotLines.push(`✕ ${errorCount}, ⚠ ${warningCount}`); 
- }
- store.roots.forEach(rootID => { 
- const {weight} = ((store.getElementByID(rootID): any): Element); 
- const maybeWeightLabel = includeWeight ? ` (${weight})` : ''; 
- // Store does not (yet) expose a way to get errors/warnings per root. 
- snapshotLines.push(`[root]${maybeWeightLabel}`); 
- for (let i = rootWeight; i < rootWeight + weight; i++) { 
- const element = store.getElementAtIndex(i); 
- if (element == null) { 
- throw Error(`Could not find element at index "${i}"`); 
- }
- const printedSelectedMarker = printSelectedMarker(i); 
- const printedElement = printElement(element, includeWeight); 
- const printedErrorsAndWarnings = printErrorsAndWarnings(element); 
- snapshotLines.push( 
- `${printedSelectedMarker}${printedElement}${printedErrorsAndWarnings}`, 
- );
- }
- rootWeight += weight; 
- });
- // Make sure the pretty-printed test align with the Store's reported number of total rows. 
- if (rootWeight !== store.numElements) { 
- throw Error( 
- `Inconsistent Store state. Individual root weights ("${rootWeight}") do not match total weight ("${store.numElements}")`, 
- );
- }
- // If roots have been unmounted, verify that they've been removed from maps. 
- // This helps ensure the Store doesn't leak memory. 
- store.assertExpectedRootMapSizes(); 
- }
- return snapshotLines.join('\n'); 
- }
- // We use JSON.parse to parse string values
- // e.g. 'foo' is not valid JSON but it is a valid string
- // so this method replaces e.g. 'foo' with "foo"
- export function sanitizeForParse(value: any): any | string { 
- if (typeof value === 'string') { 
- if ( 
- value.length >= 2 && 
- value.charAt(0) === "'" && 
- value.charAt(value.length - 1) === "'" 
- ) {
- return '"' + value.slice(1, value.length - 1) + '"'; 
- }
- }
- return value; 
- }
- export function smartParse(value: any): any | void | number { 
- switch (value) { 
- case 'Infinity': 
- return Infinity; 
- case 'NaN': 
- return NaN; 
- case 'undefined': 
- return undefined; 
- default: 
- return JSON5.parse(sanitizeForParse(value)); 
- }
- }
- export function smartStringify(value: any): string { 
- if (typeof value === 'number') { 
- if (Number.isNaN(value)) { 
- return 'NaN'; 
- } else if (!Number.isFinite(value)) { 
- return 'Infinity'; 
- }
- } else if (value === undefined) { 
- return 'undefined'; 
- }
- return JSON.stringify(value); 
- }
- // [url, row, column]
- export type Stack = [string, number, number]; 
- const STACK_DELIMETER = /\n\s+at /; 
- const STACK_SOURCE_LOCATION = /([^\s]+) \((.+):(.+):(.+)\)/; 
- export function stackToComponentSources( 
- stack: string, 
- ): Array<[string, ?Stack]> { 
- const out: Array<[string, ?Stack]> = []; 
- stack 
- .split(STACK_DELIMETER) 
- .slice(1) 
- .forEach(entry => { 
- const match = STACK_SOURCE_LOCATION.exec(entry); 
- if (match) { 
- const [, component, url, row, column] = match; 
- out.push([component, [url, parseInt(row, 10), parseInt(column, 10)]]); 
- } else {
- out.push([entry, null]); 
- }
- });
- return out;
- }