如何基于R语言源文件生成符合特定要求的静态函数调用图
静态函数调用图生成方案
需求清单
- 静态特性:无需运行代码或做性能分析,覆盖所有分支的全部可能调用路径
- 按源文件分组:函数需根据定义所在的
.R源文件分组展示 - 无需打包:开发初期,大量
.R文件暂不打包为R包 - 可编辑导出:导出格式支持拖拽节点,箭头自动贴合节点
现有实现问题
目前使用foodwebr包生成的调用图仅展示函数间调用关系,未实现按源文件分组,且导出的静态图片无法编辑,不满足需求中的第2、4项。现有代码如下:
f <- function (x) { x + 1 } g <- function (y) { f(y)*2 } h <- function (x, y) { f(x)+g(y) } library(foodwebr) x <- foodweb(h) plot(x)
解决方案
1. 实现按源文件分组+可编辑导出(HTML交互式方案)
使用codetools做静态分析,visNetwork生成可交互的分组图,导出的HTML支持拖拽节点、箭头自动适配:
# 加载依赖包 library(codetools) library(visNetwork) library(purrr) library(dplyr) # 从单个R文件提取函数、调用关系及所属文件 extract_func_info <- function(file_path) { file_env <- new.env() source(file_path, local = file_env) # 获取文件中的自定义函数 func_names <- ls(file_env)[sapply(ls(file_env), function(x) is.function(get(x, file_env)))] if (length(func_names) == 0) return(NULL) # 静态分析每个函数的调用目标(仅保留自定义函数) all_custom_funcs <- unlist(map(list.files(pattern = "\\.R$"), function(f) { e <- new.env(); source(f, local = e); ls(e)[sapply(ls(e), function(x) is.function(get(x, e)))] })) func_calls <- map_dfr(func_names, function(name) { target_funcs <- findGlobals(get(name, file_env), merge = FALSE)$functions target_funcs <- target_funcs[target_funcs %in% all_custom_funcs] if (length(target_funcs) == 0) return(NULL) data.frame(from = name, to = target_funcs, file = basename(file_path)) }) # 生成节点数据 nodes <- data.frame(id = func_names, label = func_names, group = basename(file_path)) list(nodes = nodes, edges = func_calls) } # 遍历所有R文件,整合数据 all_r_files <- list.files(pattern = "\\.R$") all_func_data <- map(all_r_files, extract_func_info) %>% compact() nodes <- bind_rows(map(all_func_data, "nodes")) %>% distinct(id, .keep_all = TRUE) edges <- bind_rows(map(all_func_data, "edges")) # 生成可交互网络图 interactive_graph <- visNetwork(nodes, edges) %>% visOptions(highlightNearest = TRUE, nodesIdSelection = TRUE) %>% visLayout(randomSeed = 456) # 固定初始布局 # 导出为可编辑的HTML文件 visSave(interactive_graph, "function_call_graph.html")
- 效果:节点按所属文件自动分组(不同颜色区分),打开HTML后可拖拽节点调整位置,箭头会自动贴合节点
- 静态分析:通过
findGlobals获取函数内的所有调用,无需运行代码,覆盖所有分支
2. 备选方案(Mermaid可编辑格式)
如果偏好文本格式的可编辑图,可使用DiagrammeR生成Mermaid代码,导出后在Mermaid编辑器中编辑:
library(DiagrammeR) library(glue) library(dplyr) # 复用上面的nodes和edges数据生成Mermaid代码 mermaid_code <- glue(" graph LR {edges %>% mutate(line = glue('{from} --> {to}')) %>% pull(line) %>% paste(collapse = '\\n')} {paste0('classDef ', unique(nodes$group), ' fill:#88f,stroke:#333,stroke-width:2px;', collapse = '')} {nodes %>% mutate(class_assign = glue('{id}:::{group}')) %>% pull(class_assign) %>% paste(collapse = '\\n')} ") # 预览图 grViz(mermaid_code) # 保存Mermaid代码到文件 writeLines(mermaid_code, "function_call_graph.mmd")
- 效果:将
.mmd文件内容复制到Mermaid编辑器中,即可拖拽节点、调整布局,箭头自动适配
内容的提问来源于stack exchange,提问作者Tomas
相关产品推荐
相关产品推荐

