Skip to content

Callers

callers

Caller/usage analysis via tree-sitter call-site detection.

Parses call-sites from tree-sitter AST to answer "who calls this function?" — traversing call nodes to find every function/method invocation across a package.

Example

from axm_ast.core.callers import find_callers results = find_callers(pkg, "greet") for r in results: ... print(f"{r.module}:{r.line} — {r.call_expression}")

extract_calls(mod, module_name=None)

Extract all function/method call-sites from a module.

Traverses the tree-sitter AST to find every call node and extracts the called symbol name, location, and context.

Parameters:

Name Type Description Default
mod ModuleInfo

Parsed module info (with path to source).

required
module_name str | None

Dotted module name for CallSite.module.

None

Returns:

Type Description
list[CallSite]

List of CallSite objects for each call in the module.

Source code in packages/axm-ast/src/axm_ast/core/callers.py
Python
def extract_calls(
    mod: ModuleInfo,
    module_name: str | None = None,
) -> list[CallSite]:
    """Extract all function/method call-sites from a module.

    Traverses the tree-sitter AST to find every ``call`` node
    and extracts the called symbol name, location, and context.

    Args:
        mod: Parsed module info (with path to source).
        module_name: Dotted module name for CallSite.module.

    Returns:
        List of CallSite objects for each call in the module.
    """
    tree = parse_file(mod.path)
    mod_name = module_name or mod.path.stem

    calls: list[CallSite] = []
    # source_bytes is unused by the visitor (node.text carries the bytes); the
    # parameter is kept for signature symmetry with the call-site helpers.
    _visit_calls(tree.root_node, mod_name, b"", calls)
    return calls

extract_references(mod)

Extract symbol names used as references (not direct calls).

Detects identifiers that appear as: - Dict values: {"key": my_func} - List elements: [func_a, func_b] - Tuple elements: (func_a, func_b) - Set elements: {func_a, func_b} - Keyword arguments: DataLoader(collate_fn=my_func) - Default parameters: def foo(callback=my_func) - Positional arguments: register(MyClass) (bare identifiers only) - Return values: return some_func / return self.method (function returned as a value, not called)

This catches dynamic dispatch patterns where functions are stored in data structures, passed as positional arguments, used as keyword arguments, or used as default parameter values.

Parameters:

Name Type Description Default
mod ModuleInfo

Parsed module info (with path to source).

required

Returns:

Type Description
set[str]

Set of symbol names referenced in non-call positions.

Source code in packages/axm-ast/src/axm_ast/core/callers.py
Python
def extract_references(mod: ModuleInfo) -> set[str]:
    """Extract symbol names used as references (not direct calls).

    Detects identifiers that appear as:
    - Dict values: ``{"key": my_func}``
    - List elements: ``[func_a, func_b]``
    - Tuple elements: ``(func_a, func_b)``
    - Set elements: ``{func_a, func_b}``
    - Keyword arguments: ``DataLoader(collate_fn=my_func)``
    - Default parameters: ``def foo(callback=my_func)``
    - Positional arguments: ``register(MyClass)`` (bare identifiers only)
    - Return values: ``return some_func`` / ``return self.method``
      (function returned as a value, not called)

    This catches dynamic dispatch patterns where functions are stored
    in data structures, passed as positional arguments, used as keyword
    arguments, or used as default parameter values.

    Args:
        mod: Parsed module info (with path to source).

    Returns:
        Set of symbol names referenced in non-call positions.
    """
    tree = parse_file(mod.path)
    refs: set[str] = set()
    _visit_references(tree.root_node, refs)
    return refs

find_callers(pkg, symbol)

Find all call-sites of a given symbol across a package.

Searches every module in the package for calls matching the given symbol name. Uses cached call-sites when available to avoid re-parsing files on repeated queries.

.. warning:: Matching is by name only. The receiver is ignored, so self.foo(), obj.foo() and a bare foo() all collapse to the name foo. This is an intrinsic tree-sitter limitation — no type inference is performed — and it may surface false-positive callers that call a distinct, like-named symbol on a different receiver. Each returned :class:~axm_ast.models.calls.CallSite carries a syntactic confidence (1.0 for direct/self calls, lower for an attribute call on another receiver) to help triage this ambiguity. The set of callers returned is never affected by confidence.

Parameters:

Name Type Description Default
pkg PackageInfo

Analyzed package info.

required
symbol str

Name of the function/method to search for.

required

Returns:

Type Description
list[CallSite]

List of CallSite objects where the symbol is called.

Example

results = find_callers(pkg, "greet") results[0].module 'cli'

Source code in packages/axm-ast/src/axm_ast/core/callers.py
Python
def find_callers(
    pkg: PackageInfo,
    symbol: str,
) -> list[CallSite]:
    """Find all call-sites of a given symbol across a package.

    Searches every module in the package for calls matching
    the given symbol name.  Uses cached call-sites when available
    to avoid re-parsing files on repeated queries.

    .. warning:: Matching is **by name only**. The receiver is ignored, so
        ``self.foo()``, ``obj.foo()`` and a bare ``foo()`` all collapse to the
        name ``foo``. This is an intrinsic tree-sitter limitation — no type
        inference is performed — and it may surface **false-positive callers**
        that call a *distinct*, like-named symbol on a different receiver.
        Each returned :class:`~axm_ast.models.calls.CallSite` carries a
        syntactic ``confidence`` (1.0 for direct/``self`` calls, lower for an
        attribute call on another receiver) to help triage this ambiguity.
        The set of callers returned is never affected by ``confidence``.

    Args:
        pkg: Analyzed package info.
        symbol: Name of the function/method to search for.

    Returns:
        List of CallSite objects where the symbol is called.

    Example:
        >>> results = find_callers(pkg, "greet")
        >>> results[0].module
        'cli'
    """
    index = _cached_call_index(pkg.root)
    if index is not None:
        # Return a fresh list (the CallSite objects are still shared with the
        # cache, matching the previous linear-scan behavior).
        return list(index.get(symbol, ()))
    return [c for c in _iter_fresh_calls(pkg) if c.symbol == symbol]

find_callers_workspace(ws, symbol)

Find all call-sites of a symbol across a workspace.

Searches every package in the workspace for calls matching the given symbol name. Module names are prefixed with pkg_name:: for disambiguation.

Parameters:

Name Type Description Default
ws WorkspaceInfo

Analyzed workspace info.

required
symbol str

Name of the function/method to search for.

required

Returns:

Type Description
list[CallSite]

List of CallSite objects where the symbol is called.

Example

results = find_callers_workspace(ws, "ToolResult") results[0].module 'axm_mcp::server'

Source code in packages/axm-ast/src/axm_ast/core/callers.py
Python
def find_callers_workspace(
    ws: WorkspaceInfo,
    symbol: str,
) -> list[CallSite]:
    """Find all call-sites of a symbol across a workspace.

    Searches every package in the workspace for calls matching
    the given symbol name. Module names are prefixed with
    ``pkg_name::`` for disambiguation.

    Args:
        ws: Analyzed workspace info.
        symbol: Name of the function/method to search for.

    Returns:
        List of CallSite objects where the symbol is called.

    Example:
        >>> results = find_callers_workspace(ws, "ToolResult")
        >>> results[0].module
        'axm_mcp::server'
    """
    all_calls: list[CallSite] = []

    for pkg in ws.packages:
        for call in find_callers(pkg, symbol):
            # find_callers returns CallSite objects shared with the package
            # cache, so copy before prefixing — mutating in place would corrupt
            # the cache and double-prefix on a later query.
            all_calls.append(
                call.model_copy(update={"module": f"{pkg.name}::{call.module}"})
            )

    return all_calls