@samitouri / QOS-React / commits / 11d7bcf88c

[DevTools] Use source maps to infer name asynchronously (#34212)

Sebastian Markbåge committed Aug 21, 2025 at 18:38 UTC 11d7bcf88cf13fa8fe6e091d2199c3905f5bb274
2 files changed +80 -15
packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspendedBy.js
+2 -15
@@ -20,6 +20,7 @@ import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/wit
20 import StackTraceView from './StackTraceView';
21 import OwnerView from './OwnerView';
22 import {meta} from '../../../hydration';
23 +import useInferredName from '../useInferredName';
24
25 import type {
26 InspectedElement,
@@ -101,21 +102,7 @@ function SuspendedByRow({
102 }: RowProps) {
103 const [isOpen, setIsOpen] = useState(false);
104 const ioInfo = asyncInfo.awaited;
104 - let name = ioInfo.name;
105 - if (name === '' || name === 'Promise') {
106 - // If all we have is a generic name, we can try to infer a better name from
107 - // the stack. We only do this if the stack has more than one frame since
108 - // otherwise it's likely to just be the name of the component which isn't better.
109 - const bestStack = ioInfo.stack || asyncInfo.stack;
110 - if (bestStack !== null && bestStack.length > 1) {
111 - // TODO: Ideally we'd get the name from the last ignore listed frame before the
112 - // first visible frame since this is the same algorithm as the Flight server uses.
113 - // Ideally, we'd also get the name from the source mapped entry instead of the
114 - // original entry. However, that would require suspending the immediate display
115 - // of these rows to first do source mapping before we can show the name.
116 - name = bestStack[0][0];
117 - }
118 - }
105 + const name = useInferredName(asyncInfo);
106 const description = ioInfo.description;
107 const longName = description === '' ? name : name + ' (' + description + ')';
108 const shortDescription = getShortDescription(name, description);
packages/react-devtools-shared/src/devtools/views/useInferredName.js new
+78
@@ -0,0 +1,78 @@
1 +import {use, useContext, useDeferredValue} from 'react';
2 +
3 +import type {ReactCallSite} from 'shared/ReactTypes';
4 +
5 +import type {SourceMappedLocation} from 'react-devtools-shared/src/symbolicateSource';
6 +
7 +import type {SerializedAsyncInfo} from 'react-devtools-shared/src/frontend/types';
8 +
9 +import FetchFileWithCachingContext from './Components/FetchFileWithCachingContext';
10 +
11 +import {symbolicateSourceWithCache} from 'react-devtools-shared/src/symbolicateSource';
12 +
13 +export default function useInferredName(
14 + asyncInfo: SerializedAsyncInfo,
15 +): string {
16 + const fetchFileWithCaching = useContext(FetchFileWithCachingContext);
17 + const name = asyncInfo.awaited.name;
18 + let inferNameFromStack = null;
19 + if (!name || name === 'Promise') {
20 + // If all we have is a generic name, we can try to infer a better name from
21 + // the stack. We only do this if the stack has more than one frame since
22 + // otherwise it's likely to just be the name of the component which isn't better.
23 + const bestStack = asyncInfo.awaited.stack || asyncInfo.stack;
24 + if (bestStack !== null && bestStack.length > 1) {
25 + inferNameFromStack = bestStack;
26 + }
27 + }
28 + // Start by not source mapping and just taking the first name and upgrade to
29 + // the better name asynchronously if we find one. Most of the time it'll just be
30 + // the top of the stack.
31 + const shouldSourceMap = useDeferredValue(inferNameFromStack !== null, false);
32 + if (inferNameFromStack !== null) {
33 + if (shouldSourceMap) {
34 + let bestMatch = '';
35 + for (let i = 0; i < inferNameFromStack.length; i++) {
36 + const callSite: ReactCallSite = inferNameFromStack[i];
37 + const [virtualFunctionName, virtualURL, virtualLine, virtualColumn] =
38 + callSite;
39 + const symbolicatedCallSite: null | SourceMappedLocation =
40 + fetchFileWithCaching !== null
41 + ? use(
42 + symbolicateSourceWithCache(
43 + fetchFileWithCaching,
44 + virtualURL,
45 + virtualLine,
46 + virtualColumn,
47 + ),
48 + )
49 + : null;
50 + if (symbolicatedCallSite === null) {
51 + // If we can't source map, we treat it as first party code. We called whatever was
52 + // the previous callsite.
53 + if (bestMatch === '') {
54 + return virtualFunctionName || name;
55 + } else {
56 + return bestMatch;
57 + }
58 + } else if (!symbolicatedCallSite.ignored) {
59 + if (bestMatch === '') {
60 + // If we had no good stack frames for internal calls, just use the last
61 + // first party function name.
62 + return symbolicatedCallSite[0] || virtualFunctionName || name;
63 + } else {
64 + return bestMatch;
65 + }
66 + } else {
67 + // This line was ignore listed, it might be the one we called into from first party.
68 + bestMatch = symbolicatedCallSite[0] || virtualFunctionName;
69 + }
70 + }
71 + return name;
72 + } else {
73 + return inferNameFromStack[0][0];
74 + }
75 + } else {
76 + return name;
77 + }
78 +}