Bug 1859531 - [devtools] Add getClosestOriginalFunctionName for the callstack original frames r=devtools-reviewers,ochameau

Differential Revision: https://phabricator.services.mozilla.com/D191185
This commit is contained in:
Hubert Boma Manilla 2024-01-16 02:27:48 +00:00
Родитель a0dc87f046
Коммит 2fa4750e87
9 изменённых файлов: 150 добавлений и 292 удалений

Просмотреть файл

@ -39430,16 +39430,66 @@ Please specify the "importAttributesKeyword" generator option, whose value can b
return symbols.classes;
}
function containsPosition$1(a, b) {
const bColumn = b.column || 0;
const startsBefore =
a.start.line < b.line ||
(a.start.line === b.line && a.start.column <= bColumn);
const endsAfter =
a.end.line > b.line || (a.end.line === b.line && a.end.column >= bColumn);
return startsBefore && endsAfter;
}
function getClosestFunctionName(location) {
const symbols = getInternalSymbols(location.source.id);
if (!symbols || !symbols.functions) {
return "";
}
const closestFunction = symbols.functions.reduce((found, currNode) => {
if (
currNode.name === "anonymous" ||
!containsPosition$1(currNode.location, {
line: location.line,
column: location.column || 0,
})
) {
return found;
}
if (!found) {
return currNode;
}
if (found.location.start.line > currNode.location.start.line) {
return found;
}
if (
found.location.start.line === currNode.location.start.line &&
found.location.start.column > currNode.location.start.column
) {
return found;
}
return currNode;
}, null);
if (!closestFunction) {
return "";
}
return closestFunction.name;
}
// This is only called from the main thread and we return a subset of attributes
function getSymbols(sourceId) {
const symbols = getInternalSymbols(sourceId);
return {
// This is used in the main thread by:
// - Outline panel
// - The `getFunctionSymbols` function
// - The mapping of frame function names
// And within the worker by `findOutOfScopeLocations`
functions: symbols.functions,
// * The `getFunctionSymbols` function which is used by the Outline, QuickOpen panels.
// * The `getClosestFunctionName` function used in the mapping of frame function names.
// * The `findOutOfScopeLocations` function use to determine in scope lines.
// functions: symbols.functions,
// The three following attributes are only used by `findBestMatchExpression` within the worker thread
// `memberExpressions`, `literals`
@ -41388,6 +41438,7 @@ Please specify the "importAttributesKeyword" generator option, whose value can b
getSymbols,
getFunctionSymbols,
getClassSymbols,
getClosestFunctionName,
getScopes,
clearSources: clearAllHelpersForSources,
hasSyntaxError,

Просмотреть файл

@ -6,7 +6,6 @@ import {
getFrames,
getBlackBoxRanges,
getSelectedFrame,
getSymbols,
} from "../../selectors";
import { isFrameBlackBoxed } from "../../utils/source";
@ -19,7 +18,8 @@ import {
} from "../../utils/location";
import { annotateFramesWithLibrary } from "../../utils/pause/frames/annotateFrames";
import { createWasmOriginalFrame } from "../../client/firefox/create";
import { getOriginalDisplayNameForOriginalLocation } from "../../reducers/pause";
import { getOriginalFunctionDisplayName } from "../sources";
function getSelectedFrameId(state, thread, frames) {
let selectedFrame = getSelectedFrame(state, thread);
@ -50,12 +50,10 @@ async function updateFrameLocationAndDisplayName(frame, thunkArgs) {
}
// As we now know that this frame relates to an original source...
// Check if we already fetched symbols for it and compute the frame's originalDisplayName.
// Otherwise it will be lazily computed by the reducer on source selection / symbols being fetched.
const symbols = getSymbols(thunkArgs.getState(), location);
const originalDisplayName = symbols
? getOriginalDisplayNameForOriginalLocation(symbols, location)
: null;
// Fetch the symbols for it and compute the frame's originalDisplayName.
const originalDisplayName = await thunkArgs.dispatch(
getOriginalFunctionDisplayName(location)
);
// As we modify frame object, fork it to force causing re-renders
return {

Просмотреть файл

@ -37,6 +37,14 @@ export const setSymbols = memoizeableAction("setSymbols", {
action: (location, thunkArgs) => doSetSymbols(location, thunkArgs),
});
export function getOriginalFunctionDisplayName(location) {
return async ({ parserWorker, dispatch }) => {
// Make sure the source for the symbols exist in the parser worker.
await dispatch(loadSourceText(location.source, location.sourceActor));
return parserWorker.getClosestFunctionName(location);
};
}
export function getFunctionSymbols(location, maxResults) {
return async ({ parserWorker, dispatch }) => {
// Make sure the source for the symbols exist in the parser worker.

Просмотреть файл

@ -10,7 +10,6 @@
*/
import { prefs } from "../utils/prefs";
import { findClosestFunction } from "../utils/ast";
// Pause state associated with an individual thread.
@ -223,23 +222,6 @@ function update(state = initialPauseState(), action) {
return updateThreadState({ frames, selectedFrameId });
}
case "MAP_FRAME_DISPLAY_NAMES": {
const { frames } = action;
return updateThreadState({ frames });
}
case "SET_SYMBOLS": {
// Ignore the beginning of this async action
if (action.status === "start") {
return state;
}
return updateFrameOriginalDisplayName(
state,
action.location.source,
action.value
);
}
case "ADD_SCOPES": {
const { status, value } = action;
const selectedFrameId = action.selectedFrame.id;
@ -442,95 +424,4 @@ function getPauseLocation(state, action) {
};
}
/**
* For frames related to non-WASM original sources, try to lookup for the function
* name in the original source. The server will provide the function name in `displayName`
* in the generated source, but not in the original source.
* This information will be stored in frame's `originalDisplayName` attribute
*/
function mapDisplayName(frame, symbols) {
// Ignore WASM original frames
if (frame.isOriginal) {
return frame;
}
// When it is a regular (non original) JS source, the server already returns a valid displayName attribute.
if (!frame.location.source.isOriginal) {
return frame;
}
// Ignore the frame if we already computed this attribute from `mapFrames` action
if (frame.originalDisplayName) {
return frame;
}
if (!symbols.functions) {
return frame;
}
const originalDisplayName = getOriginalDisplayNameForOriginalLocation(
symbols,
frame.location
);
if (!originalDisplayName) {
return frame;
}
// Fork the object to force a re-render
return { ...frame, originalDisplayName };
}
function updateFrameOriginalDisplayName(state, source, symbols) {
// Only map display names for original sources
if (!source.isOriginal) {
return state;
}
// Update all thread's frame's `originalDisplayName` attribute
// if some frames relate to the symbols source.
let newState = null;
for (const threadActorId in state.threads) {
const thread = state.threads[threadActorId];
if (!thread.frames) {
continue;
}
// Only try updating frames if at least one frame object relates to the source
// for which we just fetched the symbols
const shouldUpdateThreadFrames = thread.frames.some(
frame => frame.location.source == source
);
if (!shouldUpdateThreadFrames) {
continue;
}
// Now only process frames matching the symbols source
const frames = thread.frames.map(frame =>
frame.location.source == source ? mapDisplayName(frame, symbols) : frame
);
if (!newState) {
newState = { ...state };
}
newState.threads[threadActorId] = {
...thread,
frames,
};
}
return newState ? newState : state;
}
/**
* For a given location, return the closest function name.
*
* @param {Object} symbols
* Symbols object for the passed location.
* @param {Object} location
* A location
*/
export function getOriginalDisplayNameForOriginalLocation(symbols, location) {
if (!symbols.functions) {
return null;
}
const originalFunction = findClosestFunction(symbols, location);
return originalFunction ? originalFunction.name : null;
}
export default update;

Просмотреть файл

@ -339,16 +339,66 @@ export function getClassSymbols(sourceId) {
return symbols.classes;
}
function containsPosition(a, b) {
const bColumn = b.column || 0;
const startsBefore =
a.start.line < b.line ||
(a.start.line === b.line && a.start.column <= bColumn);
const endsAfter =
a.end.line > b.line || (a.end.line === b.line && a.end.column >= bColumn);
return startsBefore && endsAfter;
}
export function getClosestFunctionName(location) {
const symbols = getInternalSymbols(location.source.id);
if (!symbols || !symbols.functions) {
return "";
}
const closestFunction = symbols.functions.reduce((found, currNode) => {
if (
currNode.name === "anonymous" ||
!containsPosition(currNode.location, {
line: location.line,
column: location.column || 0,
})
) {
return found;
}
if (!found) {
return currNode;
}
if (found.location.start.line > currNode.location.start.line) {
return found;
}
if (
found.location.start.line === currNode.location.start.line &&
found.location.start.column > currNode.location.start.column
) {
return found;
}
return currNode;
}, null);
if (!closestFunction) {
return "";
}
return closestFunction.name;
}
// This is only called from the main thread and we return a subset of attributes
export function getSymbols(sourceId) {
const symbols = getInternalSymbols(sourceId);
return {
// This is used in the main thread by:
// - Outline panel
// - The `getFunctionSymbols` function
// - The mapping of frame function names
// And within the worker by `findOutOfScopeLocations`
functions: symbols.functions,
// * The `getFunctionSymbols` function which is used by the Outline, QuickOpen panels.
// * The `getClosestFunctionName` function used in the mapping of frame function names.
// * The `findOutOfScopeLocations` function use to determine in scope lines.
// functions: symbols.functions,
// The three following attributes are only used by `findBestMatchExpression` within the worker thread
// `memberExpressions`, `literals`

Просмотреть файл

@ -19,6 +19,7 @@ export class ParserDispatcher extends WorkerDispatcher {
getSymbols = this.task("getSymbols");
getFunctionSymbols = this.task("getFunctionSymbols");
getClassSymbols = this.task("getClassSymbols");
getClosestFunctionName = this.task("getClosestFunctionName");
async setSource(sourceId, content) {
const astSource = {

Просмотреть файл

@ -1,16 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Parser.getSymbols allSymbols 1`] = `
"functions:
[(4, 0), (6, 1)] incrementCounter(counter)
[(8, 12), (8, 27)] sum(a, b)
[(12, 2), (14, 3)] doThing()
[(15, 16), (17, 3)] doOtherThing()
[(20, 15), (20, 23)] property()
[(24, 2), (26, 3)] constructor() Ultra
[(28, 2), (30, 3)] beAwesome(person) Ultra
hasJsx: false
"hasJsx: false
hasTypes: false
@ -18,10 +9,7 @@ framework: null"
`;
exports[`Parser.getSymbols call expression 1`] = `
"functions:
[(2, 0), (2, 70)] evaluate(script, , )
hasJsx: false
"hasJsx: false
hasTypes: false
@ -29,10 +17,7 @@ framework: null"
`;
exports[`Parser.getSymbols call sites 1`] = `
"functions:
hasJsx: false
"hasJsx: false
hasTypes: false
@ -40,13 +25,7 @@ framework: null"
`;
exports[`Parser.getSymbols class 1`] = `
"functions:
[(5, 2), (7, 3)] hello() Test
[(12, 2), (15, 3)] constructor() Test
[(17, 2), (19, 3)] bar(a) Test
[(21, 8), (23, 3)] baz(b) Test
hasJsx: false
"hasJsx: false
hasTypes: false
@ -54,22 +33,7 @@ framework: null"
`;
exports[`Parser.getSymbols component 1`] = `
"functions:
[(6, 2), (9, 3)] constructor(props) Punny
[(11, 2), (11, 24)] componentDidMount() Punny
[(13, 2), (13, 14)] onClick() Punny
[(15, 2), (17, 3)] renderMe() Punny
[(19, 2), (19, 13)] render() Punny
[(29, 10), (31, 3)] render() TodoView 1
[(37, 10), (39, 3)] render() TodoClass 2
[(45, 10), (47, 3)] render() createClass 3
[(53, 10), (55, 3)] render() TodoClass 4
[(62, 0), (66, 1)] Button()
[(70, 8), (70, 21)] x()
[(72, 33), (79, 1)] createElement(color)
[(82, 26), (84, 1)] update(newColor)
hasJsx: true
"hasJsx: true
hasTypes: false
@ -77,10 +41,7 @@ framework: null"
`;
exports[`Parser.getSymbols destruct 1`] = `
"functions:
hasJsx: false
"hasJsx: false
hasTypes: false
@ -88,10 +49,7 @@ framework: null"
`;
exports[`Parser.getSymbols es6 1`] = `
"functions:
hasJsx: false
"hasJsx: false
hasTypes: false
@ -99,12 +57,7 @@ framework: null"
`;
exports[`Parser.getSymbols expression 1`] = `
"functions:
[(10, 10), (10, 23)] render() TodoView
[(23, 0), (23, 28)] params()
[(24, 11), (24, 32)] pars()
hasJsx: false
"hasJsx: false
hasTypes: false
@ -112,12 +65,7 @@ framework: null"
`;
exports[`Parser.getSymbols finds symbols in an html file 1`] = `
"functions:
[(8, 2), (10, 3)] sayHello(name)
[(22, 21), (24, 3)] capitalize(name)
[(36, 3), (39, 3)] iife()
hasJsx: false
"hasJsx: false
hasTypes: false
@ -125,10 +73,7 @@ framework: null"
`;
exports[`Parser.getSymbols flow 1`] = `
"functions:
[(2, 2), (4, 3)] renderHello(name, action, ) App
hasJsx: false
"hasJsx: false
hasTypes: true
@ -136,22 +81,7 @@ framework: null"
`;
exports[`Parser.getSymbols func 1`] = `
"functions:
[(1, 0), (3, 1)] square(n)
[(5, 7), (7, 1)] exFoo()
[(9, 0), (11, 1)] slowFoo()
[(13, 7), (15, 1)] exSlowFoo()
[(17, 0), (19, 1)] ret()
[(21, 8), (21, 21)] child()
[(23, 1), (25, 1)] anonymous()
[(28, 7), (30, 3)] name()
[(32, 2), (34, 3)] bar()
[(37, 15), (38, 1)] root()
[(40, 0), (42, 1)] test(a1, , )
[(44, 0), (44, 13)] anonymous() 1
[(46, 0), (50, 1)] ret2()
hasJsx: false
"hasJsx: false
hasTypes: false
@ -159,44 +89,7 @@ framework: null"
`;
exports[`Parser.getSymbols function names 1`] = `
"functions:
[(4, 7), (4, 20)] foo()
[(5, 9), (5, 22)] foo() 1
[(6, 6), (6, 19)] 42()
[(8, 2), (8, 10)] foo() 2
[(9, 2), (9, 12)] foo() 3
[(10, 2), (10, 9)] 42() 1
[(13, 6), (13, 19)] foo() 4
[(14, 10), (14, 23)] foo() 5
[(16, 10), (16, 22)] foo() 6
[(17, 11), (17, 23)] foo() 7
[(18, 11), (18, 23)] foo() 8
[(20, 7), (20, 19)] foo() 9
[(21, 8), (21, 20)] foo() 10
[(22, 13), (22, 25)] foo() 11
[(24, 0), (24, 35)] fn()
[(24, 19), (24, 31)] foo() 12
[(25, 0), (25, 40)] f2()
[(25, 19), (25, 31)] foo() 13
[(26, 0), (26, 45)] f3()
[(26, 24), (26, 36)] foo() 14
[(29, 8), (29, 21)] foo() Cls 15
[(30, 10), (30, 23)] foo() Cls 16
[(31, 7), (31, 20)] 42() Cls 2
[(33, 2), (33, 10)] foo() Cls 17
[(34, 2), (34, 12)] foo() Cls 18
[(35, 2), (35, 9)] 42() Cls 3
[(38, 1), (38, 13)] anonymous()
[(40, 15), (40, 28)] default()
[(44, 0), (44, 27)] a(first, second)
[(45, 0), (45, 35)] b(first = bla, second)
[(46, 0), (46, 32)] c(first = {}, second)
[(47, 0), (47, 32)] d(first = [], second)
[(48, 0), (48, 40)] e(first = defaultObj, second)
[(49, 0), (49, 40)] f(first = defaultArr, second)
[(50, 0), (50, 34)] g(first = null, second)
hasJsx: false
"hasJsx: false
hasTypes: false
@ -204,10 +97,7 @@ framework: null"
`;
exports[`Parser.getSymbols jsx 1`] = `
"functions:
hasJsx: true
"hasJsx: true
hasTypes: false
@ -215,13 +105,7 @@ framework: null"
`;
exports[`Parser.getSymbols math 1`] = `
"functions:
[(1, 0), (12, 1)] math(n)
[(2, 2), (5, 3)] square(n)
[(14, 12), (14, 25)] child()
[(15, 9), (15, 22)] child2()
hasJsx: false
"hasJsx: false
hasTypes: false
@ -229,10 +113,7 @@ framework: null"
`;
exports[`Parser.getSymbols object expressions 1`] = `
"functions:
hasJsx: false
"hasJsx: false
hasTypes: false
@ -240,10 +121,7 @@ framework: null"
`;
exports[`Parser.getSymbols optional chaining 1`] = `
"functions:
hasJsx: false
"hasJsx: false
hasTypes: false
@ -251,12 +129,7 @@ framework: null"
`;
exports[`Parser.getSymbols private fields 1`] = `
"functions:
[(2, 2), (6, 3)] constructor(secret, ) MyClass
[(13, 2), (15, 3)] #getSalt() MyClass
[(17, 2), (23, 3)] debug() MyClass
hasJsx: false
"hasJsx: false
hasTypes: false
@ -264,14 +137,7 @@ framework: null"
`;
exports[`Parser.getSymbols proto 1`] = `
"functions:
[(1, 12), (1, 25)] foo()
[(3, 12), (3, 20)] bar()
[(7, 14), (7, 27)] initialize() TodoView
[(8, 2), (10, 3)] doThing(b) TodoView
[(11, 10), (13, 3)] render() TodoView
hasJsx: false
"hasJsx: false
hasTypes: false
@ -279,10 +145,7 @@ framework: null"
`;
exports[`Parser.getSymbols react component 1`] = `
"functions:
hasJsx: false
"hasJsx: false
hasTypes: false
@ -290,10 +153,7 @@ framework: React"
`;
exports[`Parser.getSymbols regexp 1`] = `
"functions:
hasJsx: false
"hasJsx: false
hasTypes: false
@ -301,10 +161,7 @@ framework: null"
`;
exports[`Parser.getSymbols var 1`] = `
"functions:
hasJsx: false
"hasJsx: false
hasTypes: false

Просмотреть файл

@ -6,6 +6,7 @@ import {
getSymbols,
getFunctionSymbols,
getClassSymbols,
getClosestFunctionName,
clearSymbols,
} from "./getSymbols";
import { clearASTs } from "./utils/ast";
@ -31,6 +32,7 @@ self.onmessage = workerHandler({
getSymbols,
getFunctionSymbols,
getClassSymbols,
getClosestFunctionName,
getScopes,
clearSources: clearAllHelpersForSources,
hasSyntaxError,

Просмотреть файл

@ -73,7 +73,7 @@ add_task(async function () {
"binaryLookup",
"fancySort",
"fancySort",
"test",
"originalTestName",
]);
info(