Bug 1945467 - [devtools] No longer using getScopes for inline previews in CM6 r=ochameau
Highlights of this patch - Added `getBindingScopeReferencesFinder` returns a finder function which would be used to get the location and meta details for any binding reference with the scope of the immediate function containing the specified location - Use the platform scopes to determine the scope levels with `getScopeLevels` Differential Revision: https://phabricator.services.mozilla.com/D237564
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
getSelectedScope,
|
||||
} from "../../selectors/index";
|
||||
import { features } from "../../utils/prefs";
|
||||
import { getEditor } from "../../utils/editor/index";
|
||||
import { validateSelectedFrame } from "../../utils/context";
|
||||
|
||||
/**
|
||||
@@ -24,8 +25,8 @@ export function generateInlinePreview(selectedFrame) {
|
||||
if (getSelectedFrameInlinePreviews(getState())) {
|
||||
return null;
|
||||
}
|
||||
const scope = getSelectedScope(getState());
|
||||
|
||||
const scope = getSelectedScope(getState());
|
||||
if (!scope || !scope.bindings) {
|
||||
return null;
|
||||
}
|
||||
@@ -52,6 +53,8 @@ export function generateInlinePreview(selectedFrame) {
|
||||
previews[line].push(preview);
|
||||
}
|
||||
|
||||
validateSelectedFrame(getState(), selectedFrame);
|
||||
|
||||
return dispatch({
|
||||
type: "ADD_INLINE_PREVIEW",
|
||||
selectedFrame,
|
||||
@@ -63,7 +66,7 @@ export function generateInlinePreview(selectedFrame) {
|
||||
* Creates all the previews
|
||||
*
|
||||
* @param {Object} selectedFrame
|
||||
* @param {Object} scope - Scopes from the platform
|
||||
* @param {Object} scope - Scope from the platform
|
||||
* @param {Object} thunkArgs
|
||||
* @returns
|
||||
*/
|
||||
@@ -81,46 +84,79 @@ async function getPreviews(selectedFrame, scope, thunkArgs) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const originalAstScopes = await parserWorker.getScopes(selectedLocation);
|
||||
if (!originalAstScopes) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Bailout if we resumed or moved to another frame while computing the scope
|
||||
validateSelectedFrame(getState(), selectedFrame);
|
||||
|
||||
const allPreviews = [];
|
||||
let level = 0;
|
||||
while (scope && scope.bindings) {
|
||||
// All the bindings from the platform environment
|
||||
const bindings = getScopeBindings(scope);
|
||||
if (features.codemirrorNext) {
|
||||
// Get all the bindings for all scopes up until and including the first function scope.
|
||||
let allBindings = {};
|
||||
while (scope && scope.bindings) {
|
||||
const bindings = getScopeBindings(scope);
|
||||
allBindings = { ...allBindings, ...bindings };
|
||||
if (scope.type === "function") {
|
||||
break;
|
||||
}
|
||||
scope = scope.parent;
|
||||
}
|
||||
const editor = getEditor(features.codemirrorNext);
|
||||
const references = await editor.getBindingReferences(
|
||||
selectedLocation,
|
||||
Object.keys(allBindings)
|
||||
);
|
||||
|
||||
// Generate the previews for all the bindings
|
||||
const allPreviewBindingsComplete = Object.keys(bindings).map(async name => {
|
||||
// Get previews for this binding
|
||||
validateSelectedFrame(getState(), selectedFrame);
|
||||
|
||||
for (const name in references) {
|
||||
const previews = await generatePreviewsForBinding(
|
||||
originalAstScopes[level]?.bindings[name],
|
||||
references[name],
|
||||
selectedLocation.line,
|
||||
name,
|
||||
bindings[name].value,
|
||||
allBindings[name].value,
|
||||
client,
|
||||
selectedFrame.thread
|
||||
);
|
||||
|
||||
allPreviews.push(...previews);
|
||||
});
|
||||
await Promise.all(allPreviewBindingsComplete);
|
||||
|
||||
// Bailout if we resumed or moved to another frame while fetching the values from the backend
|
||||
validateSelectedFrame(getState(), selectedFrame);
|
||||
|
||||
// We need to display all variables in for all block scopes up until
|
||||
// and including the first function scope.
|
||||
if (scope.type === "function") {
|
||||
break;
|
||||
}
|
||||
level++;
|
||||
scope = scope.parent;
|
||||
} else {
|
||||
const originalAstScopes = await parserWorker.getScopes(selectedLocation);
|
||||
if (!originalAstScopes) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Bailout if we resumed or moved to another frame while computing the scope
|
||||
validateSelectedFrame(getState(), selectedFrame);
|
||||
let level = 0;
|
||||
while (scope && scope.bindings) {
|
||||
// All the bindings from the platform environment
|
||||
const bindings = getScopeBindings(scope);
|
||||
|
||||
// Generate the previews for all the bindings
|
||||
const allPreviewBindingsComplete = Object.keys(bindings).map(
|
||||
async name => {
|
||||
// Get previews for this binding
|
||||
const previews = await generatePreviewsForBinding(
|
||||
originalAstScopes[level]?.bindings[name],
|
||||
selectedLocation.line,
|
||||
name,
|
||||
bindings[name].value,
|
||||
client,
|
||||
selectedFrame.thread
|
||||
);
|
||||
|
||||
allPreviews.push(...previews);
|
||||
}
|
||||
);
|
||||
await Promise.all(allPreviewBindingsComplete);
|
||||
|
||||
// Bailout if we resumed or moved to another frame while fetching the values from the backend
|
||||
validateSelectedFrame(getState(), selectedFrame);
|
||||
|
||||
// We need to display all variables in for all block scopes up until
|
||||
// and including the first function scope.
|
||||
if (scope.type === "function") {
|
||||
break;
|
||||
}
|
||||
level++;
|
||||
scope = scope.parent;
|
||||
}
|
||||
}
|
||||
return allPreviews;
|
||||
}
|
||||
|
||||
@@ -1047,7 +1047,8 @@ class Editor extends EventEmitter {
|
||||
// investigate further Bug 1890895.
|
||||
event.target.ownerGlobal.setTimeout(() => {
|
||||
const view = editor.viewState;
|
||||
const cursorPos = this.#posToLineColumn(
|
||||
const cursorPos = lezerUtils.positionToLocation(
|
||||
view.state.doc,
|
||||
view.state.selection.main.head
|
||||
);
|
||||
handler(event, view, cursorPos.line, cursorPos.column);
|
||||
@@ -1868,12 +1869,16 @@ class Editor extends EventEmitter {
|
||||
const {
|
||||
codemirrorLanguage: { syntaxTree },
|
||||
} = this.#CodeMirror6;
|
||||
const lineObject = cm.state.doc.line(line);
|
||||
const pos = lineObject.from + column;
|
||||
const token = syntaxTree(cm.state).resolve(pos, 1);
|
||||
|
||||
const token = lezerUtils.getTreeNodeAtLocation(
|
||||
cm.state.doc,
|
||||
syntaxTree(cm.state),
|
||||
{ line, column }
|
||||
);
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
startColumn: column,
|
||||
endColumn: token.to - token.from,
|
||||
@@ -2150,7 +2155,10 @@ class Editor extends EventEmitter {
|
||||
handle: {
|
||||
markedSpans: markedSpans
|
||||
? markedSpans.map(span => {
|
||||
const { column } = this.#posToLineColumn(cm.posAtDOM(span));
|
||||
const { column } = lezerUtils.positionToLocation(
|
||||
cm.state.doc,
|
||||
cm.posAtDOM(span)
|
||||
);
|
||||
return {
|
||||
marker: { className: span.className },
|
||||
from: column,
|
||||
@@ -2198,8 +2206,8 @@ class Editor extends EventEmitter {
|
||||
name,
|
||||
klass: lezerUtils.getFunctionClass(cm.state.doc, syntaxNode),
|
||||
location: {
|
||||
start: this.#posToLineColumn(node.from),
|
||||
end: this.#posToLineColumn(node.to),
|
||||
start: lezerUtils.positionToLocation(cm.state.doc, node.from),
|
||||
end: lezerUtils.positionToLocation(cm.state.doc, node.to),
|
||||
},
|
||||
parameterNames: lezerUtils.getFunctionParameterNames(
|
||||
cm.state.doc,
|
||||
@@ -2236,8 +2244,8 @@ class Editor extends EventEmitter {
|
||||
classVarDefNode.to
|
||||
),
|
||||
location: {
|
||||
start: this.#posToLineColumn(node.from),
|
||||
end: this.#posToLineColumn(node.to),
|
||||
start: lezerUtils.positionToLocation(cm.state.doc, node.from),
|
||||
end: lezerUtils.positionToLocation(cm.state.doc, node.to),
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -2290,8 +2298,14 @@ class Editor extends EventEmitter {
|
||||
doc = cm.state.toText(sourceContent);
|
||||
tree = lezerUtils.getTree(javascriptLanguage, sourceId, sourceContent);
|
||||
}
|
||||
|
||||
const token = lezerUtils.getTreeNodeAtLocation(doc, tree, location);
|
||||
return lezerUtils.getEnclosingFunctionName(doc, token);
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const enclosingScope = lezerUtils.getEnclosingFunction(doc, token);
|
||||
return enclosingScope ? enclosingScope.funcName : "";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2321,8 +2335,8 @@ class Editor extends EventEmitter {
|
||||
computed: false,
|
||||
expression: cm.state.doc.sliceString(node.from, node.to),
|
||||
location: {
|
||||
start: this.#posToLineColumn(node.from),
|
||||
end: this.#posToLineColumn(node.to),
|
||||
start: lezerUtils.positionToLocation(cm.state.doc, node.from),
|
||||
end: lezerUtils.positionToLocation(cm.state.doc, node.to),
|
||||
},
|
||||
from: node.from,
|
||||
to: node.to,
|
||||
@@ -2435,6 +2449,73 @@ class Editor extends EventEmitter {
|
||||
return sourceLines.filter(i => i != undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the location and meta details of all the references
|
||||
* (within the scope of the immediate enclosing function of the specified location)
|
||||
* which are related to the bindings specified.
|
||||
*
|
||||
* @param {Object} location
|
||||
* @param {Array<String>} bindings - list of binding names
|
||||
* @returns {Function}
|
||||
**/
|
||||
async getBindingReferences(location, bindings) {
|
||||
const cm = editors.get(this);
|
||||
const {
|
||||
codemirrorLanguage: { syntaxTree },
|
||||
} = this.#CodeMirror6;
|
||||
|
||||
const token = lezerUtils.getTreeNodeAtLocation(
|
||||
cm.state.doc,
|
||||
syntaxTree(cm.state),
|
||||
location
|
||||
);
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const enclosingScope = lezerUtils.getEnclosingFunction(
|
||||
cm.state.doc,
|
||||
token,
|
||||
{ includeAnonymousFunctions: true }
|
||||
);
|
||||
|
||||
if (!enclosingScope) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const bindingReferences = {};
|
||||
// This should find location and meta information for the binding name specified.
|
||||
await lezerUtils.walkCursor(enclosingScope.node.cursor(), {
|
||||
filterSet: lezerUtils.nodeTypeSets.bindingReferences,
|
||||
enterVisitor: node => {
|
||||
const bindingName = cm.state.doc.sliceString(node.from, node.to);
|
||||
if (!bindings.includes(bindingName)) {
|
||||
return;
|
||||
}
|
||||
const ref = {
|
||||
start: lezerUtils.positionToLocation(cm.state.doc, node.from),
|
||||
end: lezerUtils.positionToLocation(cm.state.doc, node.to),
|
||||
};
|
||||
const syntaxNode = node.node;
|
||||
// Previews for member expressions are built of the meta property which is
|
||||
// reference of the child property and so on. e.g a.b.c
|
||||
if (syntaxNode.parent.name == lezerUtils.nodeTypes.MemberExpression) {
|
||||
ref.meta = lezerUtils.getMetaBindings(
|
||||
cm.state.doc,
|
||||
syntaxNode.parent
|
||||
);
|
||||
}
|
||||
|
||||
if (!bindingReferences[bindingName]) {
|
||||
bindingReferences[bindingName] = { refs: [] };
|
||||
}
|
||||
bindingReferences[bindingName].refs.push(ref);
|
||||
},
|
||||
});
|
||||
|
||||
return bindingReferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces whatever is in the text area with the contents of
|
||||
* the 'value' argument.
|
||||
@@ -3396,27 +3477,6 @@ class Editor extends EventEmitter {
|
||||
return inXView && inYView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the line and column values the map to the codemirror offset specified.
|
||||
* Used only for CM6
|
||||
* @param {Number} pos - Codemirror offset
|
||||
* @returns {Object} - Line column related to the position
|
||||
*/
|
||||
#posToLineColumn(pos) {
|
||||
const cm = editors.get(this);
|
||||
if (pos == null) {
|
||||
return {
|
||||
line: null,
|
||||
column: null,
|
||||
};
|
||||
}
|
||||
const line = cm.state.doc.lineAt(pos);
|
||||
return {
|
||||
line: line.number,
|
||||
column: pos - line.from,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts line/col to CM6 offset position
|
||||
* @param {Number} line - The line in the source
|
||||
@@ -3617,7 +3677,10 @@ class Editor extends EventEmitter {
|
||||
return { text: "", line: -1, column: -1 };
|
||||
}
|
||||
|
||||
const cursorPosition = this.#posToLineColumn(cursor.to);
|
||||
const cursorPosition = lezerUtils.positionToLocation(
|
||||
cm.state.doc,
|
||||
cursor.to
|
||||
);
|
||||
// The lines in CM6 are 1 based while CM5 is 0 based
|
||||
return {
|
||||
text: cursor.match[0],
|
||||
|
||||
@@ -71,6 +71,11 @@ const nodeTypeSets = {
|
||||
numberAndProperty: new Set([nodeTypes.PropertyDefinition, nodeTypes.Number]),
|
||||
memberExpression: new Set([nodeTypes.MemberExpression]),
|
||||
classes: new Set([nodeTypes.ClassDeclaration, nodeTypes.ClassExpression]),
|
||||
bindingReferences: new Set([
|
||||
nodeTypes.VariableDefinition,
|
||||
nodeTypes.VariableName,
|
||||
]),
|
||||
expressionProperty: new Set([nodeTypes.PropertyName]),
|
||||
};
|
||||
|
||||
const ast = new Map();
|
||||
@@ -133,24 +138,41 @@ function clear() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the function which immediately encloses the node (representing a location)
|
||||
* Gets the node and the function name which immediately encloses the node (representing a location)
|
||||
*
|
||||
* @param {Object} doc - The codemirror document used to retrive the part of content
|
||||
* @param {Object} node - The parser syntax node https://lezer.codemirror.net/docs/ref/#common.SyntaxNode
|
||||
* @params {Object} options
|
||||
* options.includeAnonymousFunctions - if true, allow matching anonymous functions
|
||||
* @returns
|
||||
*/
|
||||
function getEnclosingFunctionName(doc, node) {
|
||||
function getEnclosingFunction(
|
||||
doc,
|
||||
node,
|
||||
options = { includeAnonymousFunctions: false }
|
||||
) {
|
||||
let parentNode = node.parent;
|
||||
while (parentNode !== null) {
|
||||
if (nodeTypeSets.functionsVarDecl.has(parentNode.name)) {
|
||||
// For anonymous functions, we use variable declarations, but we only care about variable declarations which are part of function expressions
|
||||
if (
|
||||
parentNode.name == nodeTypes.VariableDeclaration &&
|
||||
!hasChildNodeOfType(parentNode.node, nodeTypeSets.functionExpressions)
|
||||
) {
|
||||
parentNode = parentNode.parent;
|
||||
continue;
|
||||
}
|
||||
const funcName = getFunctionName(doc, parentNode);
|
||||
if (funcName) {
|
||||
return funcName;
|
||||
if (funcName || options.includeAnonymousFunctions) {
|
||||
return {
|
||||
node: parentNode,
|
||||
funcName,
|
||||
};
|
||||
}
|
||||
}
|
||||
parentNode = parentNode.parent;
|
||||
}
|
||||
return "";
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,9 +184,36 @@ function getEnclosingFunctionName(doc, node) {
|
||||
* @returns {Object} node - https://lezer.codemirror.net/docs/ref/#common.SyntaxNodeRef
|
||||
*/
|
||||
function getTreeNodeAtLocation(doc, tree, location) {
|
||||
const line = doc.line(location.line);
|
||||
const pos = line.from + location.column;
|
||||
return tree.resolve(pos, 1);
|
||||
try {
|
||||
const line = doc.line(location.line);
|
||||
const pos = line.from + location.column;
|
||||
return tree.resolve(pos, 1);
|
||||
} catch (e) {
|
||||
// if the line is not found in the document doc.line() will throw
|
||||
console.warn(e.message);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Codemirror position to valid source location. Used only for CM6
|
||||
*
|
||||
* @param {Object} doc - The Codemirror document used to retrive the part of content
|
||||
* @param {Number} pos - Codemirror offset
|
||||
* @returns
|
||||
*/
|
||||
function positionToLocation(doc, pos) {
|
||||
if (pos == null) {
|
||||
return {
|
||||
line: null,
|
||||
column: null,
|
||||
};
|
||||
}
|
||||
const line = doc.lineAt(pos);
|
||||
return {
|
||||
line: line.number,
|
||||
column: pos - line.from,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -349,6 +398,28 @@ function getFunctionClass(doc, node) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the meta data for member expression nodes
|
||||
*
|
||||
* @param {Object} doc - The codemirror document used to retrieve the part of content
|
||||
* @param {Object} node - The parser syntax node https://lezer.codemirror.net/docs/ref/#common.SyntaxNode
|
||||
* @returns
|
||||
*/
|
||||
function getMetaBindings(doc, node) {
|
||||
if (!node || node.name !== nodeTypes.MemberExpression) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const memExpr = doc.sliceString(node.from, node.to).split(".");
|
||||
return {
|
||||
type: "member",
|
||||
start: positionToLocation(doc, node.from),
|
||||
end: positionToLocation(doc, node.to),
|
||||
property: memExpr.at(-1),
|
||||
parent: getMetaBindings(doc, node.parent),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk the syntax tree of the langauge provided
|
||||
*
|
||||
@@ -380,15 +451,34 @@ async function walkTree(view, language, options) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This enables walking a specific part of the syntax tree using the cursor
|
||||
* provided by the node (which is the parent)
|
||||
* @param {Object} cursor - https://lezer.codemirror.net/docs/ref/#common.TreeCursor
|
||||
* @param {Object} options
|
||||
* {Function} options.enterVisitor - A function that is called when a node is entered
|
||||
* {Set} options.filterSet - A set of node types which should be visited, all others should be ignored
|
||||
*/
|
||||
async function walkCursor(cursor, options) {
|
||||
await cursor.iterate(node => {
|
||||
if (options.filterSet?.has(node.name)) {
|
||||
options.enterVisitor(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getFunctionName,
|
||||
getFunctionParameterNames,
|
||||
getFunctionClass,
|
||||
getEnclosingFunctionName,
|
||||
getEnclosingFunction,
|
||||
getTreeNodeAtLocation,
|
||||
getMetaBindings,
|
||||
nodeTypes,
|
||||
nodeTypeSets,
|
||||
walkTree,
|
||||
getTree,
|
||||
clear,
|
||||
walkCursor,
|
||||
positionToLocation,
|
||||
};
|
||||
|
||||
@@ -26,7 +26,7 @@ function containsLocation(parentLocation, childLocation) {
|
||||
}
|
||||
|
||||
function getInnerLocations(locations, position) {
|
||||
// First, find the function which directly contains the specified position (line / column)
|
||||
// First, find the function which directly contains the specified position (line / column)
|
||||
let parentIndex;
|
||||
for (let i = locations.length - 1; i >= 0; i--) {
|
||||
if (containsPosition(locations[i], position)) {
|
||||
|
||||
Reference in New Issue
Block a user