Skip to content

Commit 63bbf56

Browse files
motiz88facebook-github-bot
authored andcommitted
Fix getCodeFrame resolution for watchFolder source files
Summary: The `getCodeFrame` helper in the symbolicate handler resolves stack frame file paths against `projectRoot` only. When source maps use `SourcePathsMode.ServerUrl`, the `file` field may be a server-relative URL pathname (e.g. `/src/App.js`) or a virtual-prefix path (e.g. `/[metro-watchFolders]/0/foo.js`). These resolve incorrectly against `projectRoot`. Here, we check `path.isAbsolute(file)` first (for `SourcePathsMode.Absolute` source maps), then try `filePathOfUrlDecodedPathname` (for virtual-prefix paths), and fall back to the original `path.resolve(projectRoot, file)`. Changelog: Internal Differential Revision: D104259454
1 parent f825c09 commit 63bbf56

2 files changed

Lines changed: 82 additions & 1 deletion

File tree

packages/metro/src/Server.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1366,7 +1366,16 @@ export default class Server {
13661366
continue;
13671367
}
13681368

1369-
const fileAbsolute = path.resolve(this._config.projectRoot, file ?? '');
1369+
const fileAbsolute =
1370+
file != null
1371+
? (this._routeMap.filePathOfUrlDecodedPathname(file) ??
1372+
(path.isAbsolute(file)
1373+
? file
1374+
: path.resolve(this._config.projectRoot, file)))
1375+
: null;
1376+
if (fileAbsolute == null) {
1377+
continue;
1378+
}
13701379
if (!depGraph.doesFileExist(fileAbsolute)) {
13711380
debug(
13721381
'Skipping code frame for file not in dependency graph.',

packages/metro/src/Server/__tests__/Server-test.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1350,6 +1350,78 @@ describe('processRequest', () => {
13501350
expect(result.codeFrame).toBeNull();
13511351
});
13521352

1353+
test('should return codeFrame when symbolicated file has [metro-project] prefix', async () => {
1354+
// Simulate the scenario where source maps use virtual-prefixed
1355+
// paths (sourcePaths=serverUrl) by overriding the exploded source
1356+
// map to reference /[metro-project]/mybundle.js instead of the
1357+
// absolute /root/mybundle.js. Symbolication maps bundle line 2,
1358+
// col 18 → source line 1, col 0, and produces file =
1359+
// /[metro-project]/mybundle.js. getCodeFrame must resolve this
1360+
// virtual prefix back to /root/mybundle.js to read the source.
1361+
jest
1362+
.spyOn(server, '_explodedSourceMapForBundleOptions')
1363+
.mockResolvedValue([
1364+
{
1365+
firstLine1Based: 1,
1366+
functionMap: null,
1367+
path: 'require-js',
1368+
map: [],
1369+
},
1370+
{
1371+
firstLine1Based: 2,
1372+
functionMap: null,
1373+
path: '/[metro-project]/mybundle.js',
1374+
map: [[1, 16, 1, 0]],
1375+
},
1376+
]);
1377+
1378+
const response = await makeRequest('/symbolicate', {
1379+
headers: {'content-type': 'application/json'},
1380+
data: JSON.stringify({
1381+
stack: [
1382+
{
1383+
file: `http://localhost:8081/mybundle.bundle${queryDelimiter}runModule=true`,
1384+
lineNumber: 2,
1385+
column: 18,
1386+
methodName: 'clientSideMethodName',
1387+
},
1388+
],
1389+
}),
1390+
});
1391+
1392+
const result = response._getJSON();
1393+
// Symbolication mapped the HTTP URL to the virtual-prefixed source path
1394+
expect(result.stack[0].file).toBe('/[metro-project]/mybundle.js');
1395+
expect(result.stack[0].lineNumber).toBe(1);
1396+
expect(result.stack[0].column).toBe(0);
1397+
// getCodeFrame resolved /[metro-project]/mybundle.js → /root/mybundle.js
1398+
expect(result.codeFrame).not.toBeNull();
1399+
expect(result.codeFrame.fileName).toBe('/[metro-project]/mybundle.js');
1400+
expect(result.codeFrame.content).toEqual(expect.any(String));
1401+
});
1402+
1403+
test('should return codeFrame when file is a relative path (resolved against projectRoot)', async () => {
1404+
const response = await makeRequest('/symbolicate', {
1405+
headers: {'content-type': 'application/json'},
1406+
data: JSON.stringify({
1407+
stack: [
1408+
{
1409+
file: 'mybundle.js',
1410+
lineNumber: 2,
1411+
column: 0,
1412+
methodName: 'test',
1413+
},
1414+
],
1415+
}),
1416+
});
1417+
1418+
const result = response._getJSON();
1419+
expect(result.stack[0].file).toBe('mybundle.js');
1420+
expect(result.codeFrame).not.toBeNull();
1421+
expect(result.codeFrame.fileName).toBe('mybundle.js');
1422+
expect(result.codeFrame.content).toEqual(expect.any(String));
1423+
});
1424+
13531425
// TODO: This probably should restore the *original* file before rewrite
13541426
// or normalisation.
13551427
test('should leave original file and position when cannot symbolicate (after normalisation and rewriting?)', async () => {

0 commit comments

Comments
 (0)