Skip to content

Commit 4a70286

Browse files
authored
[Flight] Encode enclosing line/column numbers and use it to align the fake function (facebook#33136)
Stacked on facebook#33135. This encodes the line/column of the enclosing function as part of the stack traces. When that information is available. I adjusted the fake function code generation so that the beginning of the arrow function aligns with these as much as possible. This ensures that when the browser tries to look up the line/column of the enclosing function, such as for getting the function name, it gets the right one. If we can't get the enclosing line/column, then we encode it at the beginning of the file. This is likely to get a miss in the source map identifiers, which means that the function name gets extracted from the runtime name instead which is better. Another thing where this is used is the in the Performance Track. Ideally that would be fixed by https://issues.chromium.org/u/1/issues/415968771 but the enclosing information is useful for other things like the function name resolution anyway. We can also use this for the "View source for this element" in React DevTools.
1 parent 0ff1d13 commit 4a70286

File tree

4 files changed

+110
-15
lines changed

4 files changed

+110
-15
lines changed

packages/react-client/src/ReactFlightClient.js

Lines changed: 93 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2226,6 +2226,8 @@ function createFakeFunction<T>(
22262226
sourceMap: null | string,
22272227
line: number,
22282228
col: number,
2229+
enclosingLine: number,
2230+
enclosingCol: number,
22292231
environmentName: string,
22302232
): FakeFunction<T> {
22312233
// This creates a fake copy of a Server Module. It represents a module that has already
@@ -2243,26 +2245,104 @@ function createFakeFunction<T>(
22432245
// This allows us to use the original source map as the source map of this fake file to
22442246
// point to the original source.
22452247
let code;
2246-
if (line <= 1) {
2247-
const minSize = encodedName.length + 7;
2248+
// Normalize line/col to zero based.
2249+
if (enclosingLine < 1) {
2250+
enclosingLine = 0;
2251+
} else {
2252+
enclosingLine--;
2253+
}
2254+
if (enclosingCol < 1) {
2255+
enclosingCol = 0;
2256+
} else {
2257+
enclosingCol--;
2258+
}
2259+
if (line < 1) {
2260+
line = 0;
2261+
} else {
2262+
line--;
2263+
}
2264+
if (col < 1) {
2265+
col = 0;
2266+
} else {
2267+
col--;
2268+
}
2269+
if (line < enclosingLine || (line === enclosingLine && col < enclosingCol)) {
2270+
// Protection against invalid enclosing information. Should not happen.
2271+
enclosingLine = 0;
2272+
enclosingCol = 0;
2273+
}
2274+
if (line < 1) {
2275+
// Fit everything on the first line.
2276+
const minCol = encodedName.length + 3;
2277+
let enclosingColDistance = enclosingCol - minCol;
2278+
if (enclosingColDistance < 0) {
2279+
enclosingColDistance = 0;
2280+
}
2281+
let colDistance = col - enclosingColDistance - minCol - 3;
2282+
if (colDistance < 0) {
2283+
colDistance = 0;
2284+
}
22482285
code =
22492286
'({' +
22502287
encodedName +
2251-
':_=>' +
2252-
' '.repeat(col < minSize ? 0 : col - minSize) +
2253-
'_()})\n' +
2254-
comment;
2288+
':' +
2289+
' '.repeat(enclosingColDistance) +
2290+
'_=>' +
2291+
' '.repeat(colDistance) +
2292+
'_()})';
2293+
} else if (enclosingLine < 1) {
2294+
// Fit just the enclosing function on the first line.
2295+
const minCol = encodedName.length + 3;
2296+
let enclosingColDistance = enclosingCol - minCol;
2297+
if (enclosingColDistance < 0) {
2298+
enclosingColDistance = 0;
2299+
}
2300+
code =
2301+
'({' +
2302+
encodedName +
2303+
':' +
2304+
' '.repeat(enclosingColDistance) +
2305+
'_=>' +
2306+
'\n'.repeat(line - enclosingLine) +
2307+
' '.repeat(col) +
2308+
'_()})';
2309+
} else if (enclosingLine === line) {
2310+
// Fit the enclosing function and callsite on same line.
2311+
let colDistance = col - enclosingCol - 3;
2312+
if (colDistance < 0) {
2313+
colDistance = 0;
2314+
}
2315+
code =
2316+
'\n'.repeat(enclosingLine - 1) +
2317+
'({' +
2318+
encodedName +
2319+
':\n' +
2320+
' '.repeat(enclosingCol) +
2321+
'_=>' +
2322+
' '.repeat(colDistance) +
2323+
'_()})';
22552324
} else {
2325+
// This is the ideal because we can always encode any position.
22562326
code =
2257-
comment +
2258-
'\n'.repeat(line - 2) +
2327+
'\n'.repeat(enclosingLine - 1) +
22592328
'({' +
22602329
encodedName +
2261-
':_=>\n' +
2262-
' '.repeat(col < 1 ? 0 : col - 1) +
2330+
':\n' +
2331+
' '.repeat(enclosingCol) +
2332+
'_=>' +
2333+
'\n'.repeat(line - enclosingLine) +
2334+
' '.repeat(col) +
22632335
'_()})';
22642336
}
22652337

2338+
if (enclosingLine < 1) {
2339+
// If the function starts at the first line, we append the comment after.
2340+
code = code + '\n' + comment;
2341+
} else {
2342+
// Otherwise we prepend the comment on the first line.
2343+
code = comment + code;
2344+
}
2345+
22662346
if (filename.startsWith('/')) {
22672347
// If the filename starts with `/` we assume that it is a file system file
22682348
// rather than relative to the current host. Since on the server fully qualified
@@ -2320,7 +2400,7 @@ function buildFakeCallStack<T>(
23202400
const frameKey = frame.join('-') + '-' + environmentName;
23212401
let fn = fakeFunctionCache.get(frameKey);
23222402
if (fn === undefined) {
2323-
const [name, filename, line, col] = frame;
2403+
const [name, filename, line, col, enclosingLine, enclosingCol] = frame;
23242404
const findSourceMapURL = response._debugFindSourceMapURL;
23252405
const sourceMap = findSourceMapURL
23262406
? findSourceMapURL(filename, environmentName)
@@ -2331,6 +2411,8 @@ function buildFakeCallStack<T>(
23312411
sourceMap,
23322412
line,
23332413
col,
2414+
enclosingLine,
2415+
enclosingCol,
23342416
environmentName,
23352417
);
23362418
// TODO: This cache should technically live on the response since the _debugFindSourceMapURL

packages/react-client/src/ReactFlightReplyClient.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1350,10 +1350,11 @@ function parseStackLocation(error: Error): null | ReactCallSite {
13501350
if (filename === '<anonymous>') {
13511351
filename = '';
13521352
}
1353+
// This is really the enclosingLine/Column.
13531354
const line = +(parsed[3] || parsed[6]);
13541355
const col = +(parsed[4] || parsed[7]);
13551356

1356-
return [name, filename, line, col];
1357+
return [name, filename, line, col, line, col];
13571358
}
13581359

13591360
export function createServerReference<A: Iterable<any>, T>(

packages/react-server/src/ReactFlightStackConfigV8.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ function collectStackTrace(
6363
// Skip everything after the bottom frame since it'll be internals.
6464
break;
6565
} else if (callSite.isNative()) {
66-
result.push([name, '', 0, 0]);
66+
result.push([name, '', 0, 0, 0, 0]);
6767
} else {
6868
// We encode complex function calls as if they're part of the function
6969
// name since we cannot simulate the complex ones and they look the same
@@ -88,7 +88,17 @@ function collectStackTrace(
8888
}
8989
const line = callSite.getLineNumber() || 0;
9090
const col = callSite.getColumnNumber() || 0;
91-
result.push([name, filename, line, col]);
91+
const enclosingLine: number =
92+
// $FlowFixMe[prop-missing]
93+
typeof callSite.getEnclosingLineNumber === 'function'
94+
? (callSite: any).getEnclosingLineNumber() || 0
95+
: 0;
96+
const enclosingCol: number =
97+
// $FlowFixMe[prop-missing]
98+
typeof callSite.getEnclosingColumnNumber === 'function'
99+
? (callSite: any).getEnclosingColumnNumber() || 0
100+
: 0;
101+
result.push([name, filename, line, col, enclosingLine, enclosingCol]);
92102
}
93103
}
94104
// At the same time we generate a string stack trace just in case someone
@@ -179,7 +189,7 @@ export function parseStackTrace(
179189
}
180190
const line = +(parsed[3] || parsed[6]);
181191
const col = +(parsed[4] || parsed[7]);
182-
parsedFrames.push([name, filename, line, col]);
192+
parsedFrames.push([name, filename, line, col, 0, 0]);
183193
}
184194
return parsedFrames;
185195
}

packages/shared/ReactTypes.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ export type ReactCallSite = [
186186
string, // file name TODO: model nested eval locations as nested arrays
187187
number, // line number
188188
number, // column number
189+
number, // enclosing line number
190+
number, // enclosing column number
189191
];
190192

191193
export type ReactStackTrace = Array<ReactCallSite>;

0 commit comments

Comments
 (0)