Skip to content

8.2. Find usages#

A typical use case for an IDE is finding where some variable, function, or type is used in the code base. In Slang this can be easily accomplished by using the Binding Graph API:

find-usages.mts
import { assertTerminalNode, TerminalKindExtensions } from "@nomicfoundation/slang/cst";
import { CompilationUnit } from "@nomicfoundation/slang/compilation";
import { assertUserFileLocation } from "@nomicfoundation/slang/bindings";
import { findTerminalNodeAt } from "../../common/find-terminal-node-at.mjs";

type Usage = {
  file: string;
  line: number;
  column: number;
};

export function findUsages(unit: CompilationUnit, fileId: string, line: number, column: number): Usage[] {
  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 definition = unit.bindingGraph.definitionAt(cursor);
  if (!definition) {
    throw new Error(`Identifier ${cursor.node.unparse()} is not a definition at ${fileId}:${line}:${column}`);
  }

  const references = definition.references();
  const usages = [];
  for (const reference of references) {
    assertUserFileLocation(reference.location);
    usages.push({
      file: reference.location.fileId,
      line: reference.location.cursor.textOffset.line,
      column: reference.location.cursor.textOffset.column,
    });
  }
  return usages;
}

For example, we can look for usages of the _count state variable defined in line 2 of the sample contract:

contract Counter {
  uint _count;
  constructor(uint initialCount) {
    _count = initialCount;
  }
  function count() public view returns (uint) {
    return _count;
  }
  function increment(uint delta) public returns (uint) {
    require(delta > 0, "Delta must be positive");
    _count += delta;
    return _count;
  }
}
test-find-usages.mts
import assert from "node:assert";
import { findUsages } from "./find-usages.mjs";
import buildSampleCompilationUnit from "../../common/sample-contract.mjs";

test("find usages", async () => {
  const unit = await buildSampleCompilationUnit();

  // the _count state variable definition
  const usages = findUsages(unit, "contract.sol", 2, 10);

  assert.deepEqual(usages, [
    { file: "contract.sol", line: 4, column: 4 },
    { file: "contract.sol", line: 7, column: 11 },
    { file: "contract.sol", line: 11, column: 4 },
    { file: "contract.sol", line: 12, column: 11 },
  ]);
});