mirror of
https://github.com/facebook/react.git
synced 2026-02-26 01:15:00 +00:00
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).
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 async 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;
|
|
}
|