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: 0Graphs 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 NAWho 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 3What 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> NAIn 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.