mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 00:20:04 +01:00
[DevTools] Unify by using ReactFunctionLocation type instead of Source (#33955)
In RSC and other stacks now we use a lot of `ReactFunctionLocation` type to represent the location of a function. I.e. the location of the beginning of the function (the enclosing line/col) that is represented by the "Source" of the function. This is also what the parent Component Stacks represents. As opposed to `ReactCallSite` which is what normal stack traces and owner stacks represent. I.e. the line/column number of the callsite into the next function. We can start sharing more code by using the `ReactFunctionLocation` type to represent the component source location and it also helps clarify which ones are function locations and which ones are callsites as we start adding more stack traces (e.g. for async debug info and owner stack traces).
This commit is contained in:
parent
bb4418d647
commit
7513996f20
20
packages/react-devtools-core/src/standalone.js
vendored
20
packages/react-devtools-core/src/standalone.js
vendored
|
|
@ -26,7 +26,7 @@ import {
|
|||
import {localStorageSetItem} from 'react-devtools-shared/src/storage';
|
||||
|
||||
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
|
||||
import type {Source} from 'react-devtools-shared/src/shared/types';
|
||||
import type {ReactFunctionLocation} from 'shared/ReactTypes';
|
||||
|
||||
export type StatusTypes = 'server-connected' | 'devtools-connected' | 'error';
|
||||
export type StatusListener = (message: string, status: StatusTypes) => void;
|
||||
|
|
@ -144,29 +144,27 @@ async function fetchFileWithCaching(url: string) {
|
|||
}
|
||||
|
||||
function canViewElementSourceFunction(
|
||||
_source: Source,
|
||||
symbolicatedSource: Source | null,
|
||||
_source: ReactFunctionLocation,
|
||||
symbolicatedSource: ReactFunctionLocation | null,
|
||||
): boolean {
|
||||
if (symbolicatedSource == null) {
|
||||
return false;
|
||||
}
|
||||
const [, sourceURL, ,] = symbolicatedSource;
|
||||
|
||||
return doesFilePathExist(symbolicatedSource.sourceURL, projectRoots);
|
||||
return doesFilePathExist(sourceURL, projectRoots);
|
||||
}
|
||||
|
||||
function viewElementSourceFunction(
|
||||
_source: Source,
|
||||
symbolicatedSource: Source | null,
|
||||
_source: ReactFunctionLocation,
|
||||
symbolicatedSource: ReactFunctionLocation | null,
|
||||
): void {
|
||||
if (symbolicatedSource == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
launchEditor(
|
||||
symbolicatedSource.sourceURL,
|
||||
symbolicatedSource.line,
|
||||
projectRoots,
|
||||
);
|
||||
const [, sourceURL, line] = symbolicatedSource;
|
||||
launchEditor(sourceURL, line, projectRoots);
|
||||
}
|
||||
|
||||
function onDisconnected() {
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ function createBridgeAndStore() {
|
|||
};
|
||||
|
||||
const viewElementSourceFunction = (source, symbolicatedSource) => {
|
||||
const {sourceURL, line, column} = symbolicatedSource
|
||||
const [, sourceURL, line, column] = symbolicatedSource
|
||||
? symbolicatedSource
|
||||
: source;
|
||||
|
||||
|
|
|
|||
|
|
@ -28,22 +28,23 @@ export type Config = {
|
|||
export function createBridge(wall: Wall): Bridge;
|
||||
export function createStore(bridge: Bridge, config?: Config): Store;
|
||||
|
||||
export type Source = {
|
||||
sourceURL: string,
|
||||
line: number,
|
||||
column: number,
|
||||
};
|
||||
export type ReactFunctionLocation = [
|
||||
string, // function name
|
||||
string, // file name TODO: model nested eval locations as nested arrays
|
||||
number, // enclosing line number
|
||||
number, // enclosing column number
|
||||
];
|
||||
export type ViewElementSource = (
|
||||
source: Source,
|
||||
symbolicatedSource: Source | null,
|
||||
source: ReactFunctionLocation,
|
||||
symbolicatedSource: ReactFunctionLocation | null,
|
||||
) => void;
|
||||
export type ViewAttributeSource = (
|
||||
id: number,
|
||||
path: Array<string | number>,
|
||||
) => void;
|
||||
export type CanViewElementSource = (
|
||||
source: Source,
|
||||
symbolicatedSource: Source | null,
|
||||
source: ReactFunctionLocation,
|
||||
symbolicatedSource: ReactFunctionLocation | null,
|
||||
) => boolean;
|
||||
|
||||
export type InitializationOptions = {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
getDisplayNameForReactElement,
|
||||
isPlainObject,
|
||||
} from 'react-devtools-shared/src/utils';
|
||||
import {stackToComponentSources} from 'react-devtools-shared/src/devtools/utils';
|
||||
import {stackToComponentLocations} from 'react-devtools-shared/src/devtools/utils';
|
||||
import {
|
||||
formatConsoleArguments,
|
||||
formatConsoleArgumentsToSingleString,
|
||||
|
|
@ -63,14 +63,17 @@ describe('utils', () => {
|
|||
|
||||
it('should parse a component stack trace', () => {
|
||||
expect(
|
||||
stackToComponentSources(`
|
||||
stackToComponentLocations(`
|
||||
at Foobar (http://localhost:3000/static/js/bundle.js:103:74)
|
||||
at a
|
||||
at header
|
||||
at div
|
||||
at App`),
|
||||
).toEqual([
|
||||
['Foobar', ['http://localhost:3000/static/js/bundle.js', 103, 74]],
|
||||
[
|
||||
'Foobar',
|
||||
['Foobar', 'http://localhost:3000/static/js/bundle.js', 103, 74],
|
||||
],
|
||||
['a', null],
|
||||
['header', null],
|
||||
['div', null],
|
||||
|
|
@ -315,12 +318,12 @@ describe('utils', () => {
|
|||
'at f (https://react.dev/_next/static/chunks/pages/%5B%5B...markdownPath%5D%5D-af2ed613aedf1d57.js:1:8519)\n' +
|
||||
'at r (https://react.dev/_next/static/chunks/pages/_app-dd0b77ea7bd5b246.js:1:498)\n',
|
||||
),
|
||||
).toEqual({
|
||||
sourceURL:
|
||||
'https://react.dev/_next/static/chunks/main-78a3b4c2aa4e4850.js',
|
||||
line: 1,
|
||||
column: 10389,
|
||||
});
|
||||
).toEqual([
|
||||
'',
|
||||
'https://react.dev/_next/static/chunks/main-78a3b4c2aa4e4850.js',
|
||||
1,
|
||||
10389,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should construct the source from highest available frame', () => {
|
||||
|
|
@ -338,12 +341,12 @@ describe('utils', () => {
|
|||
' at tt (https://react.dev/_next/static/chunks/363-3c5f1b553b6be118.js:1:165520)\n' +
|
||||
' at f (https://react.dev/_next/static/chunks/pages/%5B%5B...markdownPath%5D%5D-af2ed613aedf1d57.js:1:8519)',
|
||||
),
|
||||
).toEqual({
|
||||
sourceURL:
|
||||
'https://react.dev/_next/static/chunks/848-122f91e9565d9ffa.js',
|
||||
line: 5,
|
||||
column: 9236,
|
||||
});
|
||||
).toEqual([
|
||||
'',
|
||||
'https://react.dev/_next/static/chunks/848-122f91e9565d9ffa.js',
|
||||
5,
|
||||
9236,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should construct the source from frame, which has only url specified', () => {
|
||||
|
|
@ -353,12 +356,12 @@ describe('utils', () => {
|
|||
' at a\n' +
|
||||
' at https://react.dev/_next/static/chunks/848-122f91e9565d9ffa.js:5:9236\n',
|
||||
),
|
||||
).toEqual({
|
||||
sourceURL:
|
||||
'https://react.dev/_next/static/chunks/848-122f91e9565d9ffa.js',
|
||||
line: 5,
|
||||
column: 9236,
|
||||
});
|
||||
).toEqual([
|
||||
'',
|
||||
'https://react.dev/_next/static/chunks/848-122f91e9565d9ffa.js',
|
||||
5,
|
||||
9236,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse sourceURL correctly if it includes parentheses', () => {
|
||||
|
|
@ -368,12 +371,12 @@ describe('utils', () => {
|
|||
' at Router (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/app-router.js:181:11)\n' +
|
||||
' at ErrorBoundaryHandler (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/error-boundary.js:114:9)',
|
||||
),
|
||||
).toEqual({
|
||||
sourceURL:
|
||||
'webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/react-dev-overlay/hot-reloader-client.js',
|
||||
line: 307,
|
||||
column: 11,
|
||||
});
|
||||
).toEqual([
|
||||
'',
|
||||
'webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/react-dev-overlay/hot-reloader-client.js',
|
||||
307,
|
||||
11,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should support Firefox stack', () => {
|
||||
|
|
@ -383,12 +386,12 @@ describe('utils', () => {
|
|||
'f@https://react.dev/_next/static/chunks/pages/%5B%5B...markdownPath%5D%5D-af2ed613aedf1d57.js:1:8535\n' +
|
||||
'r@https://react.dev/_next/static/chunks/pages/_app-dd0b77ea7bd5b246.js:1:513',
|
||||
),
|
||||
).toEqual({
|
||||
sourceURL:
|
||||
'https://react.dev/_next/static/chunks/363-3c5f1b553b6be118.js',
|
||||
line: 1,
|
||||
column: 165558,
|
||||
});
|
||||
).toEqual([
|
||||
'',
|
||||
'https://react.dev/_next/static/chunks/363-3c5f1b553b6be118.js',
|
||||
1,
|
||||
165558,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -398,11 +401,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|||
exports.f = f;
|
||||
function f() { }
|
||||
//# sourceMappingURL=`;
|
||||
const result = {
|
||||
column: 16,
|
||||
line: 1,
|
||||
sourceURL: 'http://test/a.mts',
|
||||
};
|
||||
const result = ['', 'http://test/a.mts', 1, 16];
|
||||
const fs = {
|
||||
'http://test/a.mts': `export function f() {}`,
|
||||
'http://test/a.mjs.map': `{"version":3,"file":"a.mjs","sourceRoot":"","sources":["a.mts"],"names":[],"mappings":";;AAAA,cAAsB;AAAtB,SAAgB,CAAC,KAAI,CAAC"}`,
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ import type {
|
|||
ElementType,
|
||||
Plugins,
|
||||
} from 'react-devtools-shared/src/frontend/types';
|
||||
import type {Source} from 'react-devtools-shared/src/shared/types';
|
||||
import type {ReactFunctionLocation} from 'shared/ReactTypes';
|
||||
import {getSourceLocationByFiber} from './DevToolsFiberComponentStack';
|
||||
import {formatOwnerStack} from '../shared/DevToolsOwnerStack';
|
||||
|
||||
|
|
@ -162,7 +162,7 @@ type FiberInstance = {
|
|||
parent: null | DevToolsInstance,
|
||||
firstChild: null | DevToolsInstance,
|
||||
nextSibling: null | DevToolsInstance,
|
||||
source: null | string | Error | Source, // source location of this component function, or owned child stack
|
||||
source: null | string | Error | ReactFunctionLocation, // source location of this component function, or owned child stack
|
||||
logCount: number, // total number of errors/warnings last seen
|
||||
treeBaseDuration: number, // the profiled time of the last render of this subtree
|
||||
data: Fiber, // one of a Fiber pair
|
||||
|
|
@ -190,7 +190,7 @@ type FilteredFiberInstance = {
|
|||
parent: null | DevToolsInstance,
|
||||
firstChild: null | DevToolsInstance,
|
||||
nextSibling: null | DevToolsInstance,
|
||||
source: null | string | Error | Source, // always null here.
|
||||
source: null | string | Error | ReactFunctionLocation, // always null here.
|
||||
logCount: number, // total number of errors/warnings last seen
|
||||
treeBaseDuration: number, // the profiled time of the last render of this subtree
|
||||
data: Fiber, // one of a Fiber pair
|
||||
|
|
@ -222,7 +222,7 @@ type VirtualInstance = {
|
|||
parent: null | DevToolsInstance,
|
||||
firstChild: null | DevToolsInstance,
|
||||
nextSibling: null | DevToolsInstance,
|
||||
source: null | string | Error | Source, // source location of this server component, or owned child stack
|
||||
source: null | string | Error | ReactFunctionLocation, // source location of this server component, or owned child stack
|
||||
logCount: number, // total number of errors/warnings last seen
|
||||
treeBaseDuration: number, // the profiled time of the last render of this subtree
|
||||
// The latest info for this instance. This can be updated over time and the
|
||||
|
|
@ -5805,7 +5805,7 @@ export function attach(
|
|||
|
||||
function getSourceForFiberInstance(
|
||||
fiberInstance: FiberInstance,
|
||||
): Source | null {
|
||||
): ReactFunctionLocation | null {
|
||||
// Favor the owner source if we have one.
|
||||
const ownerSource = getSourceForInstance(fiberInstance);
|
||||
if (ownerSource !== null) {
|
||||
|
|
@ -5830,7 +5830,9 @@ export function attach(
|
|||
return source;
|
||||
}
|
||||
|
||||
function getSourceForInstance(instance: DevToolsInstance): Source | null {
|
||||
function getSourceForInstance(
|
||||
instance: DevToolsInstance,
|
||||
): ReactFunctionLocation | null {
|
||||
let unresolvedSource = instance.source;
|
||||
if (unresolvedSource === null) {
|
||||
// We don't have any source yet. We can try again later in case an owned child mounts later.
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import type {
|
|||
import type {InitBackend} from 'react-devtools-shared/src/backend';
|
||||
import type {TimelineDataExport} from 'react-devtools-timeline/src/types';
|
||||
import type {BackendBridge} from 'react-devtools-shared/src/bridge';
|
||||
import type {Source} from 'react-devtools-shared/src/shared/types';
|
||||
import type {ReactFunctionLocation} from 'shared/ReactTypes';
|
||||
import type Agent from './agent';
|
||||
|
||||
type BundleType =
|
||||
|
|
@ -281,7 +281,7 @@ export type InspectedElement = {
|
|||
|
||||
// List of owners
|
||||
owners: Array<SerializedElement> | null,
|
||||
source: Source | null,
|
||||
source: ReactFunctionLocation | null,
|
||||
|
||||
type: ElementType,
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {compareVersions} from 'compare-versions';
|
|||
import {dehydrate} from 'react-devtools-shared/src/hydration';
|
||||
import isArray from 'shared/isArray';
|
||||
|
||||
import type {Source} from 'react-devtools-shared/src/shared/types';
|
||||
import type {ReactFunctionLocation} from 'shared/ReactTypes';
|
||||
import type {DehydratedData} from 'react-devtools-shared/src/frontend/types';
|
||||
|
||||
export {default as formatWithStyles} from './formatWithStyles';
|
||||
|
|
@ -258,9 +258,12 @@ export const isReactNativeEnvironment = (): boolean => {
|
|||
return window.document == null;
|
||||
};
|
||||
|
||||
function extractLocation(
|
||||
url: string,
|
||||
): null | {sourceURL: string, line?: string, column?: string} {
|
||||
function extractLocation(url: string): null | {
|
||||
functionName?: string,
|
||||
sourceURL: string,
|
||||
line?: string,
|
||||
column?: string,
|
||||
} {
|
||||
if (url.indexOf(':') === -1) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -275,12 +278,15 @@ function extractLocation(
|
|||
return null;
|
||||
}
|
||||
|
||||
const functionName = ''; // TODO: Parse this in the regexp.
|
||||
const [, , sourceURL, line, column] = locationParts;
|
||||
return {sourceURL, line, column};
|
||||
return {functionName, sourceURL, line, column};
|
||||
}
|
||||
|
||||
const CHROME_STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m;
|
||||
function parseSourceFromChromeStack(stack: string): Source | null {
|
||||
function parseSourceFromChromeStack(
|
||||
stack: string,
|
||||
): ReactFunctionLocation | null {
|
||||
const frames = stack.split('\n');
|
||||
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
|
||||
for (const frame of frames) {
|
||||
|
|
@ -297,19 +303,22 @@ function parseSourceFromChromeStack(stack: string): Source | null {
|
|||
continue;
|
||||
}
|
||||
|
||||
const {sourceURL, line = '1', column = '1'} = location;
|
||||
const {functionName, sourceURL, line = '1', column = '1'} = location;
|
||||
|
||||
return {
|
||||
return [
|
||||
functionName || '',
|
||||
sourceURL,
|
||||
line: parseInt(line, 10),
|
||||
column: parseInt(column, 10),
|
||||
};
|
||||
parseInt(line, 10),
|
||||
parseInt(column, 10),
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseSourceFromFirefoxStack(stack: string): Source | null {
|
||||
function parseSourceFromFirefoxStack(
|
||||
stack: string,
|
||||
): ReactFunctionLocation | null {
|
||||
const frames = stack.split('\n');
|
||||
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
|
||||
for (const frame of frames) {
|
||||
|
|
@ -325,13 +334,14 @@ function parseSourceFromFirefoxStack(stack: string): Source | null {
|
|||
continue;
|
||||
}
|
||||
|
||||
const {sourceURL, line = '1', column = '1'} = location;
|
||||
const {functionName, sourceURL, line = '1', column = '1'} = location;
|
||||
|
||||
return {
|
||||
return [
|
||||
functionName || '',
|
||||
sourceURL,
|
||||
line: parseInt(line, 10),
|
||||
column: parseInt(column, 10),
|
||||
};
|
||||
parseInt(line, 10),
|
||||
parseInt(column, 10),
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
@ -339,7 +349,7 @@ function parseSourceFromFirefoxStack(stack: string): Source | null {
|
|||
|
||||
export function parseSourceFromComponentStack(
|
||||
componentStack: string,
|
||||
): Source | null {
|
||||
): ReactFunctionLocation | null {
|
||||
if (componentStack.match(CHROME_STACK_REGEXP)) {
|
||||
return parseSourceFromChromeStack(componentStack);
|
||||
}
|
||||
|
|
@ -347,13 +357,13 @@ export function parseSourceFromComponentStack(
|
|||
return parseSourceFromFirefoxStack(componentStack);
|
||||
}
|
||||
|
||||
let collectedLocation: Source | null = null;
|
||||
let collectedLocation: ReactFunctionLocation | null = null;
|
||||
|
||||
function collectStackTrace(
|
||||
error: Error,
|
||||
structuredStackTrace: CallSite[],
|
||||
): string {
|
||||
let result: null | Source = null;
|
||||
let result: null | ReactFunctionLocation = null;
|
||||
// Collect structured stack traces from the callsites.
|
||||
// We mirror how V8 serializes stack frames and how we later parse them.
|
||||
for (let i = 0; i < structuredStackTrace.length; i++) {
|
||||
|
|
@ -386,11 +396,7 @@ function collectStackTrace(
|
|||
// Skip eval etc. without source url. They don't have location.
|
||||
continue;
|
||||
}
|
||||
result = {
|
||||
sourceURL,
|
||||
line: line,
|
||||
column: col,
|
||||
};
|
||||
result = [name, sourceURL, line, col];
|
||||
}
|
||||
}
|
||||
// At the same time we generate a string stack trace just in case someone
|
||||
|
|
@ -404,7 +410,9 @@ function collectStackTrace(
|
|||
return stack;
|
||||
}
|
||||
|
||||
export function parseSourceFromOwnerStack(error: Error): Source | null {
|
||||
export function parseSourceFromOwnerStack(
|
||||
error: Error,
|
||||
): ReactFunctionLocation | null {
|
||||
// First attempt to collected the structured data using prepareStackTrace.
|
||||
collectedLocation = null;
|
||||
const previousPrepare = Error.prepareStackTrace;
|
||||
|
|
|
|||
|
|
@ -260,9 +260,9 @@ export function convertInspectedElementBackendToFrontend(
|
|||
rendererPackageName,
|
||||
rendererVersion,
|
||||
rootType,
|
||||
// Previous backend implementations (<= 5.0.1) have a different interface for Source, with fileName.
|
||||
// This gates the source features for only compatible backends: >= 5.0.2
|
||||
source: source && source.sourceURL ? source : null,
|
||||
// Previous backend implementations (<= 6.1.5) have a different interface for Source.
|
||||
// This gates the source features for only compatible backends: >= 6.1.6
|
||||
source: Array.isArray(source) ? source : null,
|
||||
type,
|
||||
owners:
|
||||
owners === null
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
import JSON5 from 'json5';
|
||||
|
||||
import type {ReactFunctionLocation} from 'shared/ReactTypes';
|
||||
import type {Element} from 'react-devtools-shared/src/frontend/types';
|
||||
import type {StateContext} from './views/Components/TreeContext';
|
||||
import type Store from './store';
|
||||
|
|
@ -188,16 +189,13 @@ export function smartStringify(value: any): string {
|
|||
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(
|
||||
export function stackToComponentLocations(
|
||||
stack: string,
|
||||
): Array<[string, ?Stack]> {
|
||||
const out: Array<[string, ?Stack]> = [];
|
||||
): Array<[string, ?ReactFunctionLocation]> {
|
||||
const out: Array<[string, ?ReactFunctionLocation]> = [];
|
||||
stack
|
||||
.split(STACK_DELIMETER)
|
||||
.slice(1)
|
||||
|
|
@ -205,7 +203,10 @@ export function stackToComponentSources(
|
|||
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)]]);
|
||||
out.push([
|
||||
component,
|
||||
[component, url, parseInt(row, 10), parseInt(column, 10)],
|
||||
]);
|
||||
} else {
|
||||
out.push([entry, null]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import Skeleton from './Skeleton';
|
|||
|
||||
import styles from './InspectedElement.css';
|
||||
|
||||
import type {Source} from 'react-devtools-shared/src/shared/types';
|
||||
import type {ReactFunctionLocation} from 'shared/ReactTypes';
|
||||
|
||||
export type Props = {};
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ export default function InspectedElementWrapper(_: Props): React.Node {
|
|||
|
||||
const fetchFileWithCaching = useContext(FetchFileWithCachingContext);
|
||||
|
||||
const symbolicatedSourcePromise: null | Promise<Source | null> =
|
||||
const symbolicatedSourcePromise: null | Promise<ReactFunctionLocation | null> =
|
||||
React.useMemo(() => {
|
||||
if (inspectedElement == null) return null;
|
||||
if (fetchFileWithCaching == null) return Promise.resolve(null);
|
||||
|
|
@ -58,7 +58,7 @@ export default function InspectedElementWrapper(_: Props): React.Node {
|
|||
const {source} = inspectedElement;
|
||||
if (source == null) return Promise.resolve(null);
|
||||
|
||||
const {sourceURL, line, column} = source;
|
||||
const [, sourceURL, line, column] = source;
|
||||
return symbolicateSourceWithCache(
|
||||
fetchFileWithCaching,
|
||||
sourceURL,
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@ import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/wit
|
|||
|
||||
import ViewElementSourceContext from './ViewElementSourceContext';
|
||||
|
||||
import type {Source as InspectedElementSource} from 'react-devtools-shared/src/shared/types';
|
||||
import type {ReactFunctionLocation} from 'shared/ReactTypes';
|
||||
import styles from './InspectedElementSourcePanel.css';
|
||||
|
||||
type Props = {
|
||||
source: InspectedElementSource,
|
||||
symbolicatedSourcePromise: Promise<InspectedElementSource | null>,
|
||||
source: ReactFunctionLocation,
|
||||
symbolicatedSourcePromise: Promise<ReactFunctionLocation | null>,
|
||||
};
|
||||
|
||||
function InspectedElementSourcePanel({
|
||||
|
|
@ -62,7 +62,7 @@ function InspectedElementSourcePanel({
|
|||
function CopySourceButton({source, symbolicatedSourcePromise}: Props) {
|
||||
const symbolicatedSource = React.use(symbolicatedSourcePromise);
|
||||
if (symbolicatedSource == null) {
|
||||
const {sourceURL, line, column} = source;
|
||||
const [, sourceURL, line, column] = source;
|
||||
const handleCopy = withPermissionsCheck(
|
||||
{permissions: ['clipboardWrite']},
|
||||
() => copy(`${sourceURL}:${line}:${column}`),
|
||||
|
|
@ -75,7 +75,7 @@ function CopySourceButton({source, symbolicatedSourcePromise}: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
const {sourceURL, line, column} = symbolicatedSource;
|
||||
const [, sourceURL, line, column] = symbolicatedSource;
|
||||
const handleCopy = withPermissionsCheck(
|
||||
{permissions: ['clipboardWrite']},
|
||||
() => copy(`${sourceURL}:${line}:${column}`),
|
||||
|
|
@ -109,14 +109,8 @@ function FormattedSourceString({source, symbolicatedSourcePromise}: Props) {
|
|||
}
|
||||
}, [source, symbolicatedSource]);
|
||||
|
||||
let sourceURL, line;
|
||||
if (symbolicatedSource == null) {
|
||||
sourceURL = source.sourceURL;
|
||||
line = source.line;
|
||||
} else {
|
||||
sourceURL = symbolicatedSource.sourceURL;
|
||||
line = symbolicatedSource.line;
|
||||
}
|
||||
const [, sourceURL, line] =
|
||||
symbolicatedSource == null ? source : symbolicatedSource;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ import type {
|
|||
} from 'react-devtools-shared/src/frontend/types';
|
||||
import type {HookNames} from 'react-devtools-shared/src/frontend/types';
|
||||
import type {ToggleParseHookNames} from './InspectedElementContext';
|
||||
import type {Source} from 'react-devtools-shared/src/shared/types';
|
||||
import type {ReactFunctionLocation} from 'shared/ReactTypes';
|
||||
|
||||
type Props = {
|
||||
element: Element,
|
||||
|
|
@ -43,7 +43,7 @@ type Props = {
|
|||
inspectedElement: InspectedElement,
|
||||
parseHookNames: boolean,
|
||||
toggleParseHookNames: ToggleParseHookNames,
|
||||
symbolicatedSourcePromise: Promise<Source | null>,
|
||||
symbolicatedSourcePromise: Promise<ReactFunctionLocation | null>,
|
||||
};
|
||||
|
||||
export default function InspectedElementView({
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import Button from '../Button';
|
|||
import ViewElementSourceContext from './ViewElementSourceContext';
|
||||
import Skeleton from './Skeleton';
|
||||
|
||||
import type {Source as InspectedElementSource} from 'react-devtools-shared/src/shared/types';
|
||||
import type {ReactFunctionLocation} from 'shared/ReactTypes';
|
||||
import type {
|
||||
CanViewElementSource,
|
||||
ViewElementSource,
|
||||
|
|
@ -24,8 +24,8 @@ const {useCallback, useContext} = React;
|
|||
|
||||
type Props = {
|
||||
canViewSource: ?boolean,
|
||||
source: ?InspectedElementSource,
|
||||
symbolicatedSourcePromise: Promise<InspectedElementSource | null> | null,
|
||||
source: ?ReactFunctionLocation,
|
||||
symbolicatedSourcePromise: Promise<ReactFunctionLocation | null> | null,
|
||||
};
|
||||
|
||||
function InspectedElementViewSourceButton({
|
||||
|
|
@ -52,8 +52,8 @@ function InspectedElementViewSourceButton({
|
|||
|
||||
type ActualSourceButtonProps = {
|
||||
canViewSource: ?boolean,
|
||||
source: ?InspectedElementSource,
|
||||
symbolicatedSourcePromise: Promise<InspectedElementSource | null> | null,
|
||||
source: ?ReactFunctionLocation,
|
||||
symbolicatedSourcePromise: Promise<ReactFunctionLocation | null> | null,
|
||||
canViewElementSourceFunction: CanViewElementSource | null,
|
||||
viewElementSourceFunction: ViewElementSource | null,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,22 +11,22 @@ import * as React from 'react';
|
|||
import Button from 'react-devtools-shared/src/devtools/views/Button';
|
||||
import ButtonIcon from 'react-devtools-shared/src/devtools/views/ButtonIcon';
|
||||
|
||||
import type {Source} from 'react-devtools-shared/src/shared/types';
|
||||
import type {ReactFunctionLocation} from 'shared/ReactTypes';
|
||||
|
||||
type Props = {
|
||||
editorURL: string,
|
||||
source: Source,
|
||||
symbolicatedSourcePromise: Promise<Source | null>,
|
||||
source: ReactFunctionLocation,
|
||||
symbolicatedSourcePromise: Promise<ReactFunctionLocation | null>,
|
||||
};
|
||||
|
||||
function checkConditions(
|
||||
editorURL: string,
|
||||
source: Source,
|
||||
source: ReactFunctionLocation,
|
||||
): {url: URL | null, shouldDisableButton: boolean} {
|
||||
try {
|
||||
const url = new URL(editorURL);
|
||||
|
||||
let sourceURL = source.sourceURL;
|
||||
let [, sourceURL, ,] = source;
|
||||
|
||||
// Check if sourceURL is a correct URL, which has a protocol specified
|
||||
if (sourceURL.includes('://')) {
|
||||
|
|
|
|||
|
|
@ -50,21 +50,21 @@ import type {FetchFileWithCaching} from './Components/FetchFileWithCachingContex
|
|||
import type {HookNamesModuleLoaderFunction} from 'react-devtools-shared/src/devtools/views/Components/HookNamesModuleLoaderContext';
|
||||
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
|
||||
import type {BrowserTheme} from 'react-devtools-shared/src/frontend/types';
|
||||
import type {Source} from 'react-devtools-shared/src/shared/types';
|
||||
import type {ReactFunctionLocation} from 'shared/ReactTypes';
|
||||
|
||||
export type TabID = 'components' | 'profiler';
|
||||
|
||||
export type ViewElementSource = (
|
||||
source: Source,
|
||||
symbolicatedSource: Source | null,
|
||||
source: ReactFunctionLocation,
|
||||
symbolicatedSource: ReactFunctionLocation | null,
|
||||
) => void;
|
||||
export type ViewAttributeSource = (
|
||||
id: number,
|
||||
path: Array<string | number>,
|
||||
) => void;
|
||||
export type CanViewElementSource = (
|
||||
source: Source,
|
||||
symbolicatedSource: Source | null,
|
||||
source: ReactFunctionLocation,
|
||||
symbolicatedSource: ReactFunctionLocation | null,
|
||||
) => boolean;
|
||||
|
||||
export type Props = {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import {
|
|||
formatTimestamp,
|
||||
getSchedulingEventLabel,
|
||||
} from 'react-devtools-timeline/src/utils/formatting';
|
||||
import {stackToComponentSources} from 'react-devtools-shared/src/devtools/utils';
|
||||
import {stackToComponentLocations} from 'react-devtools-shared/src/devtools/utils';
|
||||
import {copy} from 'clipboard-js';
|
||||
import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/withPermissionsCheck';
|
||||
|
||||
|
|
@ -63,9 +63,9 @@ function SchedulingEventInfo({eventInfo}: SchedulingEventProps) {
|
|||
</Button>
|
||||
</div>
|
||||
<ul className={styles.List}>
|
||||
{stackToComponentSources(componentStack).map(
|
||||
([displayName, stack], index) => {
|
||||
if (stack == null) {
|
||||
{stackToComponentLocations(componentStack).map(
|
||||
([displayName, location], index) => {
|
||||
if (location == null) {
|
||||
return (
|
||||
<li key={index}>
|
||||
<Button
|
||||
|
|
@ -79,16 +79,14 @@ function SchedulingEventInfo({eventInfo}: SchedulingEventProps) {
|
|||
|
||||
// TODO: We should support symbolication here as well, but
|
||||
// symbolicating the whole stack can be expensive
|
||||
const [sourceURL, line, column] = stack;
|
||||
const source = {sourceURL, line, column};
|
||||
const canViewSource =
|
||||
canViewElementSourceFunction == null ||
|
||||
canViewElementSourceFunction(source, null);
|
||||
canViewElementSourceFunction(location, null);
|
||||
|
||||
const viewSource =
|
||||
!canViewSource || viewElementSourceFunction == null
|
||||
? () => null
|
||||
: () => viewElementSourceFunction(source, null);
|
||||
: () => viewElementSourceFunction(location, null);
|
||||
|
||||
return (
|
||||
<li key={index}>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import type {
|
|||
Dehydrated,
|
||||
Unserializable,
|
||||
} from 'react-devtools-shared/src/hydration';
|
||||
import type {Source} from 'react-devtools-shared/src/shared/types';
|
||||
import type {ReactFunctionLocation} from 'shared/ReactTypes';
|
||||
|
||||
export type BrowserTheme = 'dark' | 'light';
|
||||
|
||||
|
|
@ -246,7 +246,7 @@ export type InspectedElement = {
|
|||
owners: Array<SerializedElement> | null,
|
||||
|
||||
// Location of component in source code.
|
||||
source: Source | null,
|
||||
source: ReactFunctionLocation | null,
|
||||
|
||||
type: ElementType,
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
export type Source = {
|
||||
sourceURL: string,
|
||||
line: number,
|
||||
column: number,
|
||||
};
|
||||
|
|
@ -9,17 +9,20 @@
|
|||
|
||||
import SourceMapConsumer from 'react-devtools-shared/src/hooks/SourceMapConsumer';
|
||||
|
||||
import type {Source} from 'react-devtools-shared/src/shared/types';
|
||||
import type {ReactFunctionLocation} from 'shared/ReactTypes';
|
||||
import type {FetchFileWithCaching} from 'react-devtools-shared/src/devtools/views/Components/FetchFileWithCachingContext';
|
||||
|
||||
const symbolicationCache: Map<string, Promise<Source | null>> = new Map();
|
||||
const symbolicationCache: Map<
|
||||
string,
|
||||
Promise<ReactFunctionLocation | null>,
|
||||
> = new Map();
|
||||
|
||||
export async function symbolicateSourceWithCache(
|
||||
fetchFileWithCaching: FetchFileWithCaching,
|
||||
sourceURL: string,
|
||||
line: number, // 1-based
|
||||
column: number, // 1-based
|
||||
): Promise<Source | null> {
|
||||
): Promise<ReactFunctionLocation | null> {
|
||||
const key = `${sourceURL}:${line}:${column}`;
|
||||
const cachedPromise = symbolicationCache.get(key);
|
||||
if (cachedPromise != null) {
|
||||
|
|
@ -43,7 +46,7 @@ export async function symbolicateSource(
|
|||
sourceURL: string,
|
||||
lineNumber: number, // 1-based
|
||||
columnNumber: number, // 1-based
|
||||
): Promise<Source | null> {
|
||||
): Promise<ReactFunctionLocation | null> {
|
||||
const resource = await fetchFileWithCaching(sourceURL).catch(() => null);
|
||||
if (resource == null) {
|
||||
return null;
|
||||
|
|
@ -75,6 +78,7 @@ export async function symbolicateSource(
|
|||
try {
|
||||
const parsedSourceMap = JSON.parse(sourceMap);
|
||||
const consumer = SourceMapConsumer(parsedSourceMap);
|
||||
const functionName = ''; // TODO: Parse function name from sourceContent.
|
||||
const {
|
||||
sourceURL: possiblyURL,
|
||||
line,
|
||||
|
|
@ -91,7 +95,7 @@ export async function symbolicateSource(
|
|||
// sourceMapURL = https://react.dev/script.js.map
|
||||
void new URL(possiblyURL); // test if it is a valid URL
|
||||
|
||||
return {sourceURL: possiblyURL, line, column};
|
||||
return [functionName, possiblyURL, line, column];
|
||||
} catch (e) {
|
||||
// This is not valid URL
|
||||
if (
|
||||
|
|
@ -101,7 +105,7 @@ export async function symbolicateSource(
|
|||
possiblyURL.slice(1).startsWith(':\\\\')
|
||||
) {
|
||||
// This is an absolute path
|
||||
return {sourceURL: possiblyURL, line, column};
|
||||
return [functionName, possiblyURL, line, column];
|
||||
}
|
||||
|
||||
// This is a relative path
|
||||
|
|
@ -110,7 +114,7 @@ export async function symbolicateSource(
|
|||
possiblyURL,
|
||||
sourceMapURL,
|
||||
).toString();
|
||||
return {sourceURL: absoluteSourcePath, line, column};
|
||||
return [functionName, absoluteSourcePath, line, column];
|
||||
}
|
||||
} catch (e) {
|
||||
return null;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user