8.3. Jump to definition#
Another often used feature of an IDE is the ability to jump to the definition of a given identifier. Again, we can use the Binding Graph API to do it:
jump-to-definition.mts
import { assertTerminalNode, TerminalKindExtensions } from "@nomicfoundation/slang/cst";
import { CompilationUnit } from "@nomicfoundation/slang/compilation";
import { findTerminalNodeAt } from "../../common/find-terminal-node-at.mjs";
type Target = {
file: string;
line: number;
column: number;
};
export function jumpToDefinition(unit: CompilationUnit, fileId: string, line: number, column: number): Target {
const file = unit.file(fileId);
if (!file) {
throw new Error(`${fileId} not found in compilation unit`);
}
const cursor = findTerminalNodeAt(file.createTreeCursor(), line, column);
if (!cursor) {
throw new Error(`${fileId}:${line}:${column} is not a valid text location`);
}
assertTerminalNode(cursor.node);
if (!TerminalKindExtensions.isIdentifier(cursor.node.kind)) {
// location is not a valid identifier
throw new Error(`Could not find a valid identifier at ${fileId}:${line}:${column}`);
}
const reference = unit.bindingGraph.referenceAt(cursor);
if (!reference) {
throw new Error(`Identifier ${cursor.node.unparse()} is not a reference at ${fileId}:${line}:${column}`);
}
const definitions = reference.definitions();
if (definitions.length == 0) {
throw new Error(`${cursor.node.unparse()} is not defined`);
}
// we take the first definition arbitrarily
const location = definitions[0].nameLocation;
if (!location.isUserFileLocation()) {
throw new Error(`${cursor.node.unparse()} is a built-in`);
}
return {
file: location.fileId,
line: location.cursor.textOffset.line,
column: location.cursor.textOffset.column,
};
}
The following example shows jumping to the definition of the parameter delta
in line 11:
test-jump-to-definition.mts
import assert from "node:assert";
import { jumpToDefinition } from "./jump-to-definition.mjs";
import buildSampleCompilationUnit from "../../common/sample-contract.mjs";
test("jump to definition", async () => {
const unit = await buildSampleCompilationUnit();
// the reference to `delta` in the assignment addition
const definition = jumpToDefinition(unit, "contract.sol", 11, 16);
assert.deepEqual(definition, { file: "contract.sol", line: 9, column: 26 });
});