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

DevTools: Hook names optimizations #22403

Merged
merged 3 commits into from
Sep 23, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
241 changes: 241 additions & 0 deletions packages/react-devtools-shared/src/hooks/SourceMapConsumer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
/**
* Copyright (c) Facebook, Inc. and its 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 {withSyncPerfMeasurements} from 'react-devtools-shared/src/PerformanceLoggingUtils';
import {decode} from 'sourcemap-codec';

import type {
IndexSourceMap,
BasicSourceMap,
MixedSourceMap,
} from './SourceMapTypes';

type SearchPosition = {|
columnNumber: number,
lineNumber: number,
|};

type ResultPosition = {|
column: number,
line: number,
sourceContent: string,
sourceURL: string,
|};

export type SourceMapConsumerType = {|
originalPositionFor: SearchPosition => ResultPosition,
|};

type Mappings = Array<Array<Array<number>>>;

export default function SourceMapConsumer(
sourceMapJSON: MixedSourceMap,
): SourceMapConsumerType {
if (sourceMapJSON.sections != null) {
return IndexedSourceMapConsumer(((sourceMapJSON: any): IndexSourceMap));
} else {
return BasicSourceMapConsumer(((sourceMapJSON: any): BasicSourceMap));
}
}

function BasicSourceMapConsumer(sourceMapJSON: BasicSourceMap) {
const decodedMappings: Mappings = withSyncPerfMeasurements(
'Decoding source map mappings with sourcemap-codec',
() => decode(sourceMapJSON.mappings),
);

function originalPositionFor({
columnNumber,
lineNumber,
}: SearchPosition): ResultPosition {
// Error.prototype.stack columns are 1-based (like most IDEs) but ASTs are 0-based.
const targetColumnNumber = columnNumber - 1;

const lineMappings = decodedMappings[lineNumber - 1];

let nearestEntry = null;

let startIndex = 0;
let stopIndex = lineMappings.length - 1;
bvaughn marked this conversation as resolved.
Show resolved Hide resolved
let index = -1;
while (startIndex <= stopIndex) {
index = Math.floor((stopIndex + startIndex) / 2);
nearestEntry = lineMappings[index];

const currentColumn = nearestEntry[0];
if (currentColumn === targetColumnNumber) {
break;
} else {
if (currentColumn > targetColumnNumber) {
if (stopIndex - index > 0) {
stopIndex = index;
} else {
index = stopIndex;
break;
}
} else {
if (index - startIndex > 0) {
startIndex = index;
} else {
index = startIndex;
break;
}
}
}
}

// We have found either the exact element, or the next-closest element.
// However there may be more than one such element.
// Make sure we always return the smallest of these.
while (index > 0) {
const previousEntry = lineMappings[index - 1];
const currentColumn = previousEntry[0];
if (currentColumn !== targetColumnNumber) {
break;
}
index--;
}

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

const sourceIndex = nearestEntry[1];
const sourceContent =
sourceMapJSON.sourcesContent != null
? sourceMapJSON.sourcesContent[sourceIndex]
: null;
const sourceURL = sourceMapJSON.sources[sourceIndex] ?? null;
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),
};
}

return (({
originalPositionFor,
}: any): SourceMapConsumerType);
}

function IndexedSourceMapConsumer(sourceMapJSON: IndexSourceMap) {
let lastOffset = {
line: -1,
column: 0,
};

const sections = sourceMapJSON.sections.map(section => {
const offset = section.offset;
const offsetLine = offset.line;
const offsetColumn = offset.column;

if (
offsetLine < lastOffset.line ||
(offsetLine === lastOffset.line && offsetColumn < lastOffset.column)
) {
throw new Error('Section offsets must be ordered and non-overlapping.');
}

lastOffset = offset;

return {
// The offset fields are 0-based, but we use 1-based indices when encoding/decoding from VLQ.
generatedLine: offsetLine + 1,
generatedColumn: offsetColumn + 1,
sourceMapConsumer: new SourceMapConsumer(section.map),
};
});

function originalPositionFor({
columnNumber,
lineNumber,
}: SearchPosition): ResultPosition {
// Error.prototype.stack columns are 1-based (like most IDEs) but ASTs are 0-based.
const targetColumnNumber = columnNumber - 1;

let section = null;

let startIndex = 0;
let stopIndex = sections.length - 1;
let index = -1;
while (startIndex <= stopIndex) {
index = Math.floor((stopIndex + startIndex) / 2);
section = sections[index];

const currentLine = section.generatedLine;
if (currentLine === lineNumber) {
const currentColumn = section.generatedColumn;
if (currentColumn === lineNumber) {
break;
} else {
if (currentColumn > targetColumnNumber) {
if (stopIndex - index > 0) {
stopIndex = index;
} else {
index = stopIndex;
break;
}
} else {
if (index - startIndex > 0) {
startIndex = index;
} else {
index = startIndex;
break;
}
}
}
} else {
if (currentLine > lineNumber) {
if (stopIndex - index > 0) {
stopIndex = index;
} else {
index = stopIndex;
break;
}
} else {
if (index - startIndex > 0) {
startIndex = index;
} else {
index = startIndex;
break;
}
}
}
}

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

return section.sourceMapConsumer.originalPositionFor({
columnNumber,
lineNumber,
});
}

return (({
originalPositionFor,
}: any): SourceMapConsumerType);
}
2 changes: 0 additions & 2 deletions packages/react-devtools-shared/src/hooks/astUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ export type Position = {|
column: number,
|};

export type SourceConsumer = any;

export type SourceFileASTWithHookDetails = {
sourceFileAST: File,
line: number,
Expand Down
Loading