mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
0825d019be
Stacked on #34094. This shows the I/O stack if available. If it's not available or if it has a different owner (like if it was passed in) then we show the `"awaited at:"` stack below it so you can see where it started and where it was awaited. If it's the same owner this tends to be unnecessary noise. We could maybe be smarter if the stacks are very different then you might want to show both even with the same owner. <img width="517" height="478" alt="Screenshot 2025-08-04 at 11 57 28 AM" src="https://github.com/user-attachments/assets/2dbfbed4-4671-4a5f-8e6e-ebec6fe8a1b7" /> Additionally, this adds an inferred await if there's no owner and no stack for the await. The inferred await of a function/class component is just the owner. No stack. Because the stack trace would be the return value. This will also be the case if you use throw-a-Promise. The inferred await in the child position of a built-in is the JSX location of that await like if you pass a promise to a child. This inference already happens when you pass a Promise from RSC so in this case it already has an await - so this is mainly for client promises.
130 lines
3.9 KiB
JavaScript
130 lines
3.9 KiB
JavaScript
/**
|
|
* 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 SourceMapConsumer from 'react-devtools-shared/src/hooks/SourceMapConsumer';
|
|
|
|
import type {ReactFunctionLocation} from 'shared/ReactTypes';
|
|
import type {FetchFileWithCaching} from 'react-devtools-shared/src/devtools/views/Components/FetchFileWithCachingContext';
|
|
|
|
const symbolicationCache: Map<
|
|
string,
|
|
Promise<ReactFunctionLocation | null>,
|
|
> = new Map();
|
|
|
|
export function symbolicateSourceWithCache(
|
|
fetchFileWithCaching: FetchFileWithCaching,
|
|
sourceURL: string,
|
|
line: number, // 1-based
|
|
column: number, // 1-based
|
|
): Promise<ReactFunctionLocation | null> {
|
|
const key = `${sourceURL}:${line}:${column}`;
|
|
const cachedPromise = symbolicationCache.get(key);
|
|
if (cachedPromise != null) {
|
|
return cachedPromise;
|
|
}
|
|
|
|
const promise = symbolicateSource(
|
|
fetchFileWithCaching,
|
|
sourceURL,
|
|
line,
|
|
column,
|
|
);
|
|
symbolicationCache.set(key, promise);
|
|
|
|
return promise;
|
|
}
|
|
|
|
const SOURCE_MAP_ANNOTATION_PREFIX = 'sourceMappingURL=';
|
|
export async function symbolicateSource(
|
|
fetchFileWithCaching: FetchFileWithCaching,
|
|
sourceURL: string,
|
|
lineNumber: number, // 1-based
|
|
columnNumber: number, // 1-based
|
|
): Promise<ReactFunctionLocation | null> {
|
|
const resource = await fetchFileWithCaching(sourceURL).catch(() => null);
|
|
if (resource == null) {
|
|
return null;
|
|
}
|
|
|
|
const resourceLines = resource.split(/[\r\n]+/);
|
|
for (let i = resourceLines.length - 1; i >= 0; --i) {
|
|
const resourceLine = resourceLines[i];
|
|
|
|
// In case there is empty last line
|
|
if (!resourceLine) continue;
|
|
// Not an annotation? Stop looking for a source mapping url.
|
|
if (!resourceLine.startsWith('//#')) break;
|
|
|
|
if (resourceLine.includes(SOURCE_MAP_ANNOTATION_PREFIX)) {
|
|
const sourceMapAnnotationStartIndex = resourceLine.indexOf(
|
|
SOURCE_MAP_ANNOTATION_PREFIX,
|
|
);
|
|
const sourceMapAt = resourceLine.slice(
|
|
sourceMapAnnotationStartIndex + SOURCE_MAP_ANNOTATION_PREFIX.length,
|
|
resourceLine.length,
|
|
);
|
|
|
|
const sourceMapURL = new URL(sourceMapAt, sourceURL).toString();
|
|
const sourceMap = await fetchFileWithCaching(sourceMapURL).catch(
|
|
() => null,
|
|
);
|
|
if (sourceMap != null) {
|
|
try {
|
|
const parsedSourceMap = JSON.parse(sourceMap);
|
|
const consumer = SourceMapConsumer(parsedSourceMap);
|
|
const functionName = ''; // TODO: Parse function name from sourceContent.
|
|
const {
|
|
sourceURL: possiblyURL,
|
|
line,
|
|
column,
|
|
} = consumer.originalPositionFor({
|
|
lineNumber, // 1-based
|
|
columnNumber, // 1-based
|
|
});
|
|
|
|
if (possiblyURL === null) {
|
|
return null;
|
|
}
|
|
try {
|
|
// sourceMapURL = https://react.dev/script.js.map
|
|
void new URL(possiblyURL); // test if it is a valid URL
|
|
|
|
return [functionName, possiblyURL, line, column];
|
|
} catch (e) {
|
|
// This is not valid URL
|
|
if (
|
|
// sourceMapURL = /file
|
|
possiblyURL.startsWith('/') ||
|
|
// sourceMapURL = C:\\...
|
|
possiblyURL.slice(1).startsWith(':\\\\')
|
|
) {
|
|
// This is an absolute path
|
|
return [functionName, possiblyURL, line, column];
|
|
}
|
|
|
|
// This is a relative path
|
|
// possiblyURL = x.js.map, sourceMapURL = https://react.dev/script.js.map
|
|
const absoluteSourcePath = new URL(
|
|
possiblyURL,
|
|
sourceMapURL,
|
|
).toString();
|
|
return [functionName, absoluteSourcePath, line, column];
|
|
}
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|