mirror of
https://github.com/facebook/react.git
synced 2026-02-26 02:35:00 +00:00
* Facebook -> Meta in copyright rg --files | xargs sed -i 's#Copyright (c) Facebook, Inc. and its affiliates.#Copyright (c) Meta Platforms, Inc. and affiliates.#g' * Manual tweaks
275 lines
8.4 KiB
JavaScript
275 lines
8.4 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 strict-local
|
|
*/
|
|
|
|
import type {
|
|
HookMap,
|
|
HookMapEntry,
|
|
HookMapLine,
|
|
HookMapMappings,
|
|
} from './generateHookMap';
|
|
import type {Position} from './astUtils';
|
|
import {NO_HOOK_NAME} from './astUtils';
|
|
|
|
/**
|
|
* Finds the Hook name assigned to a given location in the source code,
|
|
* and a HookMap extracted from an extended source map.
|
|
* The given location must correspond to the location in the *original*
|
|
* source code (i.e. *not* the generated one).
|
|
*
|
|
* Note that all locations in the source code are guaranteed to map
|
|
* to a name, including a sentinel value that represents a missing
|
|
* Hook name: '<no-hook>'.
|
|
*
|
|
* For more details on the format of the HookMap, see generateHookMap
|
|
* and the tests for that function and this function.
|
|
*/
|
|
export function getHookNameForLocation(
|
|
location: Position,
|
|
hookMap: HookMap,
|
|
): string | null {
|
|
const {names, mappings} = hookMap;
|
|
|
|
// The HookMap mappings are grouped by lines, so first we look up
|
|
// which line of mappings covers the target location.
|
|
// Note that we expect to find a line since all the locations in the
|
|
// source code are guaranteed to map to a name, including a '<no-hook>'
|
|
// name.
|
|
const foundLine = binSearch(location, mappings, compareLinePositions);
|
|
if (foundLine == null) {
|
|
throw new Error(
|
|
`Expected to find a line in the HookMap that covers the target location at line: ${location.line}, column: ${location.column}`,
|
|
);
|
|
}
|
|
|
|
let foundEntry;
|
|
const foundLineNumber = getLineNumberFromLine(foundLine);
|
|
// The line found in the mappings will never be larger than the target
|
|
// line, and vice-versa, so if the target line doesn't match the found
|
|
// line, we immediately know that it must correspond to the last mapping
|
|
// entry for that line.
|
|
if (foundLineNumber !== location.line) {
|
|
foundEntry = foundLine[foundLine.length - 1];
|
|
} else {
|
|
foundEntry = binSearch(location, foundLine, compareColumnPositions);
|
|
}
|
|
|
|
if (foundEntry == null) {
|
|
throw new Error(
|
|
`Expected to find a mapping in the HookMap that covers the target location at line: ${location.line}, column: ${location.column}`,
|
|
);
|
|
}
|
|
|
|
const foundNameIndex = getHookNameIndexFromEntry(foundEntry);
|
|
if (foundNameIndex == null) {
|
|
throw new Error(
|
|
`Expected to find a name index in the HookMap that covers the target location at line: ${location.line}, column: ${location.column}`,
|
|
);
|
|
}
|
|
const foundName = names[foundNameIndex];
|
|
if (foundName == null) {
|
|
throw new Error(
|
|
`Expected to find a name in the HookMap that covers the target location at line: ${location.line}, column: ${location.column}`,
|
|
);
|
|
}
|
|
|
|
if (foundName === NO_HOOK_NAME) {
|
|
return null;
|
|
}
|
|
return foundName;
|
|
}
|
|
|
|
function binSearch<T>(
|
|
location: Position,
|
|
items: T[],
|
|
compare: (
|
|
location: Position,
|
|
items: T[],
|
|
index: number,
|
|
) => {index: number | null, direction: number},
|
|
): T | null {
|
|
let count = items.length;
|
|
let index = 0;
|
|
let firstElementIndex = 0;
|
|
let step;
|
|
|
|
while (count > 0) {
|
|
index = firstElementIndex;
|
|
step = Math.floor(count / 2);
|
|
index += step;
|
|
|
|
const comparison = compare(location, items, index);
|
|
if (comparison.direction === 0) {
|
|
if (comparison.index == null) {
|
|
throw new Error('Expected an index when matching element is found.');
|
|
}
|
|
firstElementIndex = comparison.index;
|
|
break;
|
|
}
|
|
|
|
if (comparison.direction > 0) {
|
|
index++;
|
|
firstElementIndex = index;
|
|
count -= step + 1;
|
|
} else {
|
|
count = step;
|
|
}
|
|
}
|
|
|
|
return firstElementIndex != null ? items[firstElementIndex] : null;
|
|
}
|
|
|
|
/**
|
|
* Compares the target line location to the current location
|
|
* given by the provided index.
|
|
*
|
|
* If the target line location matches the current location, returns
|
|
* the index of the matching line in the mappings. In order for a line
|
|
* to match, the target line must match the line exactly, or be within
|
|
* the line range of the current line entries and the adjacent line
|
|
* entries.
|
|
*
|
|
* If the line doesn't match, returns the search direction for the
|
|
* next step in the binary search.
|
|
*/
|
|
function compareLinePositions(
|
|
location: Position,
|
|
mappings: HookMapMappings,
|
|
index: number,
|
|
): {index: number | null, direction: number} {
|
|
const startIndex = index;
|
|
const start = mappings[startIndex];
|
|
if (start == null) {
|
|
throw new Error(`Unexpected line missing in HookMap at index ${index}.`);
|
|
}
|
|
const startLine = getLineNumberFromLine(start);
|
|
|
|
let endLine;
|
|
let endIndex = index + 1;
|
|
const end = mappings[endIndex];
|
|
if (end != null) {
|
|
endLine = getLineNumberFromLine(end);
|
|
} else {
|
|
endIndex = startIndex;
|
|
endLine = startLine;
|
|
}
|
|
|
|
// When the line matches exactly, return the matching index
|
|
if (startLine === location.line) {
|
|
return {index: startIndex, direction: 0};
|
|
}
|
|
if (endLine === location.line) {
|
|
return {index: endIndex, direction: 0};
|
|
}
|
|
|
|
// If we're at the end of the mappings, and the target line is greater
|
|
// than the current line, then this final line must cover the
|
|
// target location, so we return it.
|
|
if (location.line > endLine && end == null) {
|
|
return {index: endIndex, direction: 0};
|
|
}
|
|
|
|
// If the location is within the current line and the adjacent one,
|
|
// we know that the target location must be covered by the current line.
|
|
if (startLine < location.line && location.line < endLine) {
|
|
return {index: startIndex, direction: 0};
|
|
}
|
|
|
|
// Otherwise, return the next direction in the search.
|
|
return {index: null, direction: location.line - startLine};
|
|
}
|
|
|
|
/**
|
|
* Compares the target column location to the current location
|
|
* given by the provided index.
|
|
*
|
|
* If the target column location matches the current location, returns
|
|
* the index of the matching entry in the mappings. In order for a column
|
|
* to match, the target column must match the column exactly, or be within
|
|
* the column range of the current entry and the adjacent entry.
|
|
*
|
|
* If the column doesn't match, returns the search direction for the
|
|
* next step in the binary search.
|
|
*/
|
|
function compareColumnPositions(
|
|
location: Position,
|
|
line: HookMapLine,
|
|
index: number,
|
|
): {index: number | null, direction: number} {
|
|
const startIndex = index;
|
|
const start = line[index];
|
|
if (start == null) {
|
|
throw new Error(
|
|
`Unexpected mapping missing in HookMap line at index ${index}.`,
|
|
);
|
|
}
|
|
const startColumn = getColumnNumberFromEntry(start);
|
|
|
|
let endColumn;
|
|
let endIndex = index + 1;
|
|
const end = line[endIndex];
|
|
if (end != null) {
|
|
endColumn = getColumnNumberFromEntry(end);
|
|
} else {
|
|
endIndex = startIndex;
|
|
endColumn = startColumn;
|
|
}
|
|
|
|
// When the column matches exactly, return the matching index
|
|
if (startColumn === location.column) {
|
|
return {index: startIndex, direction: 0};
|
|
}
|
|
if (endColumn === location.column) {
|
|
return {index: endIndex, direction: 0};
|
|
}
|
|
|
|
// If we're at the end of the entries for this line, and the target
|
|
// column is greater than the current column, then this final entry
|
|
// must cover the target location, so we return it.
|
|
if (location.column > endColumn && end == null) {
|
|
return {index: endIndex, direction: 0};
|
|
}
|
|
|
|
// If the location is within the current column and the adjacent one,
|
|
// we know that the target location must be covered by the current entry.
|
|
if (startColumn < location.column && location.column < endColumn) {
|
|
return {index: startIndex, direction: 0};
|
|
}
|
|
|
|
// Otherwise, return the next direction in the search.
|
|
return {index: null, direction: location.column - startColumn};
|
|
}
|
|
|
|
function getLineNumberFromLine(line: HookMapLine): number {
|
|
return getLineNumberFromEntry(line[0]);
|
|
}
|
|
|
|
function getLineNumberFromEntry(entry: HookMapEntry): number {
|
|
const lineNumber = entry[0];
|
|
if (lineNumber == null) {
|
|
throw new Error('Unexpected line number missing in entry in HookMap');
|
|
}
|
|
return lineNumber;
|
|
}
|
|
|
|
function getColumnNumberFromEntry(entry: HookMapEntry): number {
|
|
const columnNumber = entry[1];
|
|
if (columnNumber == null) {
|
|
throw new Error('Unexpected column number missing in entry in HookMap');
|
|
}
|
|
return columnNumber;
|
|
}
|
|
|
|
function getHookNameIndexFromEntry(entry: HookMapEntry): number {
|
|
const hookNameIndex = entry[2];
|
|
if (hookNameIndex == null) {
|
|
throw new Error('Unexpected hook name index missing in entry in HookMap');
|
|
}
|
|
return hookNameIndex;
|
|
}
|