Skip to contents

explain_call() opens up one call. The code graph opens up a whole package: every definition and every call between them, queryable from the console.

Build a graph

build_graph() accepts either a source directory (a package repo or any folder of R files — parsed statically, with exact files and line numbers, nothing executed) or the name of an installed package (introspected through its namespace).

For this vignette we create a tiny package source tree:

pkg_dir <- file.path(tempdir(), "demo_pkg", "R")
dir.create(pkg_dir, recursive = TRUE, showWarnings = FALSE)

writeLines(c(
  "prepare <- function(x) scale_it(x[!is.na(x)])",
  "scale_it <- function(x) (x - mean(x)) / sd(x)",
  "analyze <- function(x) summarize_it(prepare(x))",
  "summarize_it <- function(y) stats::fivenum(y)"
), file.path(pkg_dir, "core.R"))

g <- build_graph(dirname(pkg_dir), cache = FALSE)
g
#> 
#> ── insideR code graph ──────────────────────────────────────────────────────────
#> Target: /tmp/Rtmpdk3Mq7/demo_pkg (source mode)
#> Nodes: 4 (function: 4)
#> Call edges: 7 (3 internal)
#> Unresolved references: 0

Graphs are cached by default (.insider_graph.rds in the directory, or the user cache directory for installed packages) and rebuilt automatically when files change; cache = FALSE here keeps the vignette self-contained.

Query it

Find definitions by pattern, optionally filtered by kind ("function", "s4_class", "r6_method", …):

graph_search(g, "it$")
#>           name     kind     file line_start line_end exported
#> 1     scale_it function R/core.R          2        2       NA
#> 2 summarize_it function R/core.R          4        4       NA

Who calls a function — with the file and line of every call site:

graph_callers(g, "prepare")
#>    caller     kind     file line
#> 1 analyze function R/core.R    3

What a function calls, separating package-internal calls from calls into base R and other packages:

graph_callees(g, "analyze")
#>         callee internal     file line
#> 1      prepare     TRUE R/core.R    3
#> 2 summarize_it     TRUE R/core.R    3
graph_callees(g, "scale_it", internal_only = TRUE)
#> [1] callee   internal file     line    
#> <0 rows> (or 0-length row.names)

And one definition with line-numbered source:

graph_node(g, "scale_it")
#> 
#> ── scale_it ────────────────────────────────────────────────────────────────────
#> Kind: function
#> Location: R/core.R:2-2
#> 2 | scale_it <- function(x) (x - mean(x)) / sd(x)

Installed packages

The same queries work on anything you have installed:

g_stats <- build_graph("stats", cache = FALSE)
head(graph_callers(g_stats, "var"))
#>                caller     kind file line
#> 1 ansari.test.default function <NA>   NA
#> 2              bw.bcv function <NA>   NA
#> 3              bw.nrd function <NA>   NA
#> 4              bw.ucv function <NA>   NA
#> 5     density.default function <NA>   NA
#> 6 predict.HoltWinters function <NA>   NA

In installed mode, call edges come from static analysis of the loaded functions, so they carry no line numbers unless the package was installed with source references — but callers, callees, and source display all work.

From graph to call

The two levels compose: use the graph to find where behavior lives, then explain_call() / unpack_call() on a concrete call to extract and safely modify exactly that path.