Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: path handling in react devtools #29199

Merged
merged 7 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions packages/react-devtools-shared/src/__tests__/utils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
REACT_STRICT_MODE_TYPE as StrictMode,
} from 'shared/ReactSymbols';
import {createElement} from 'react';
import {symbolicateSource} from '../symbolicateSource';

describe('utils', () => {
describe('getDisplayName', () => {
Expand Down Expand Up @@ -385,6 +386,35 @@ describe('utils', () => {
});
});

describe('symbolicateSource', () => {
const source = `"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.f = f;
function f() { }
//# sourceMappingURL=`;
const result = {
column: 16,
line: 1,
sourceURL: 'http://test/a.mts',
};
const fs = {
'http://test/a.mts': `export function f() {}`,
'http://test/a.mjs.map': `{"version":3,"file":"a.mjs","sourceRoot":"","sources":["a.mts"],"names":[],"mappings":";;AAAA,cAAsB;AAAtB,SAAgB,CAAC,KAAI,CAAC"}`,
'http://test/a.mjs': `${source}a.mjs.map`,
'http://test/b.mjs': `${source}./a.mjs.map`,
'http://test/c.mjs': `${source}http://test/a.mjs.map`,
'http://test/d.mjs': `${source}/a.mjs.map`,
};
const fetchFileWithCaching = async (url: string) => fs[url] || null;
it('should parse source map urls', async () => {
const run = url => symbolicateSource(fetchFileWithCaching, url, 4, 10);
await expect(run('http://test/a.mjs')).resolves.toStrictEqual(result);
await expect(run('http://test/b.mjs')).resolves.toStrictEqual(result);
await expect(run('http://test/c.mjs')).resolves.toStrictEqual(result);
await expect(run('http://test/d.mjs')).resolves.toStrictEqual(result);
});
});

describe('formatConsoleArguments', () => {
it('works with empty arguments list', () => {
expect(formatConsoleArguments(...[])).toEqual([]);
Expand Down
15 changes: 4 additions & 11 deletions packages/react-devtools-shared/src/hooks/SourceMapConsumer.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ type SearchPosition = {
type ResultPosition = {
column: number,
line: number,
sourceContent: string,
sourceURL: string,
sourceContent: string | null,
sourceURL: string | null,
};

export type SourceMapConsumerType = {
Expand Down Expand Up @@ -118,18 +118,11 @@ function BasicSourceMapConsumer(sourceMapJSON: BasicSourceMap) {
const line = nearestEntry[2] + 1;
const column = nearestEntry[3];

if (sourceContent === null || sourceURL === null) {
// TODO maybe fall back to the runtime source instead of throwing?
throw Error(
`Could not find original source for line:${lineNumber} and column:${columnNumber}`,
);
}

return {
column,
line,
sourceContent: ((sourceContent: any): string),
sourceURL: ((sourceURL: any): string),
sourceContent: ((sourceContent: any): string | null),
sourceURL: ((sourceURL: any): string | null),
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,11 @@ function parseSourceAST(
columnNumber,
lineNumber,
});
if (sourceContent === null || sourceURL === null) {
throw Error(
`Could not find original source for line:${lineNumber} and column:${columnNumber}`,
);
}

originalSourceColumnNumber = column;
originalSourceLineNumber = line;
Expand Down
33 changes: 19 additions & 14 deletions packages/react-devtools-shared/src/symbolicateSource.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export async function symbolicateSourceWithCache(
}

const SOURCE_MAP_ANNOTATION_PREFIX = 'sourceMappingURL=';
async function symbolicateSource(
export async function symbolicateSource(
fetchFileWithCaching: FetchFileWithCaching,
sourceURL: string,
lineNumber: number, // 1-based
Expand All @@ -63,11 +63,12 @@ async function symbolicateSource(
const sourceMapAnnotationStartIndex = resourceLine.indexOf(
SOURCE_MAP_ANNOTATION_PREFIX,
);
const sourceMapURL = resourceLine.slice(
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,
);
Expand All @@ -84,29 +85,33 @@ async function symbolicateSource(
columnNumber, // 1-based
});

if (possiblyURL === null) {
return null;
}
Jack-Works marked this conversation as resolved.
Show resolved Hide resolved
try {
void new URL(possiblyURL); // This is a valid URL
// sourceMapURL=https://...
Jack-Works marked this conversation as resolved.
Show resolved Hide resolved
void new URL(possiblyURL); // test if it is a valid URL
const normalizedURL = normalizeUrl(possiblyURL);

return {sourceURL: normalizedURL, line, column};
} catch (e) {
// This is not valid URL
if (possiblyURL.startsWith('/')) {
if (
// sourceMapURL=/file
possiblyURL.startsWith('/') ||
// sourceMapURL=C:\\...
possiblyURL.slice(1).startsWith(':\\\\')
Jack-Works marked this conversation as resolved.
Show resolved Hide resolved
) {
// This is an absolute path
return {sourceURL: possiblyURL, line, column};
}

// This is a relative path
const [sourceMapAbsolutePathWithoutQueryParameters] =
sourceMapURL.split(/[?#&]/);

const absoluteSourcePath =
sourceMapAbsolutePathWithoutQueryParameters +
(sourceMapAbsolutePathWithoutQueryParameters.endsWith('/')
? ''
: '/') +
possiblyURL;

// sourceMapURL=x.js.map
Jack-Works marked this conversation as resolved.
Show resolved Hide resolved
const absoluteSourcePath = new URL(
possiblyURL,
sourceMapURL,
).toString();
return {sourceURL: absoluteSourcePath, line, column};
}
} catch (e) {
Expand Down
2 changes: 1 addition & 1 deletion packages/react-devtools-shared/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1017,7 +1017,7 @@ export function backendToFrontendSerializedElementMapper(
};
}

// This is a hacky one to just support this exact case.
// Chrome normalizes urls like webpack-internals:// but new URL don't, so cannot use new URL here.
export function normalizeUrl(url: string): string {
return url.replace('/./', '/');
}