imputesrcref imputes transparent srcref metadata for injected brace calls ({) in R function ASTs.
In R, a srcref is source-location metadata (line/column ranges) attached to
parsed objects when source retention is enabled (for example with
options(keep.source = TRUE)).
This package solves a finer-grained mapping problem for control-flow
expressions written without braces (for example if (x) f() else g()) and
for unbraced function-call arguments. It adds transparent wrappers and imputes
srcrefs from parse data so individual components can be mapped to source
precisely.
It uses the parser token tables getParseData for it.
Given code like:
if (x && y) f() else g()impute_srcrefs() rewrites target positions to:
if ({x} && {y}) {f()} else {g()}and:
g({x+1}, {f({y+1})})and assigns srcrefs to injected { calls using parse-data-derived source spans.
impute_srcrefs(fn)- Impute missing control-flow/function-call braces and transparent srcrefs for one function.
source_impute_srcrefs(file, envir = parent.frame(), ...)- Source an R file and patch all changed/new functions in the target environment.
impute_package_srcrefs(package, include_internal = TRUE, ...)- Patch package namespace functions. Works for any package whose functions
retain
srcrefmetadata (the default for source installs); cached parse data is no longer required.
- Patch package namespace functions. Works for any package whose functions
retain
get_impute_blacklist(include_default = TRUE)- Inspect call names excluded from generic argument wrapping.
set_impute_blacklist(functions, append = TRUE)- Add or replace user blacklist entries.
reset_impute_blacklist()- Clear user blacklist entries.
impute_package_srcrefs() requires each function to carry a srcref
attribute. Source installs retain this by default. Binary installs often do
too, but if srcref is missing the function will be skipped (failed[i] == "no srcref"). To force retention explicitly, install from source with:
install.packages("<package>", INSTALL_opts = "--with-keep.source")Cached parse data (--with-keep.parse.data) is no longer required —
impute_srcrefs() re-parses from the function's source lines when no parse
data is attached.
For functions that truly lack srcref (e.g. some generated closures),
options(imputesrcref.allow_deparse_fallback = TRUE) will impute against a
deparsed copy of the function instead, at the cost of source line numbers
shifting to the deparsed layout.
options(keep.source = TRUE)
f <- eval(parse(text = "function(x, y) if (x && y) f() else g()", keep.source = TRUE)[[1]])
g <- impute_srcrefs(f)
genv <- new.env(parent = baseenv())
res <- source_impute_srcrefs("path/to/file.R", envir = env)
res$functionsres <- impute_package_srcrefs("stringr", include_internal = TRUE)
res$patched_count
head(res$failed[!is.na(res$failed)])Returned fields are:
packagefn_namesfailed(NAmeans successfully patched; otherwise a short reason such as"no srcref"or an error message)patched_count
Generic call wrapping excludes SPECIALSXP primitive names by default (from builtins()).
Add your own exclusions with:
set_impute_blacklist(c("str_c", "paste0"))Inspect active values:
head(get_impute_blacklist())
get_impute_blacklist(include_default = FALSE)impute_srcrefs() requires function srcref metadata by default.
For functions created without srcrefs, opt into deparse-based fallback explicitly:
options(imputesrcref.allow_deparse_fallback = TRUE)Snapshot tests compare generated output against committed .out golden files.
They include regression coverage for:
- package-style
srcrefline mappings (sr[7:8]vssr[1:3]) - zero-formals functions (
function()) Run tests in compare mode:
Rscript -e "testthat::test_dir('tests/testthat', load_package = 'source')"Refresh snapshots:
UPDATE_SNAPSHOTS=1 Rscript -e "testthat::test_dir('tests/testthat', load_package = 'source')"By default, mismatches fail. With UPDATE_SNAPSHOTS=1, the snapshot file is rewritten.
tests/testthat/test-package-srcref-imputation.R runs whole-package srcref
imputation over a corpus of popular packages (data.table, dplyr, fs,
ggplot2, glue, jsonlite, stringr, zoo). For each it asserts that no
function fails for an unexpected reason and that a sample of patched functions
is idempotent and srcref-text-consistent (the line-accuracy guarantee).
These tests are slow and require the corpus packages to be installed with
srcref retention, so they are gated behind FULL_TEST=1 and skip gracefully
when a package is absent or was installed without srcref.
The Makefile provides targets for this. The corpus is installed into a
separate library (CORPUS_LIB, default ~/Rlib_test) so your main library is
left untouched:
make corpus-install # install the 8 corpus packages with srcref (run once)
make test-full # run the full suite including the corpus testsUse a different library with make test-full CORPUS_LIB=/path/to/lib. Or run
it directly:
FULL_TEST=1 Rscript -e \
".libPaths(c('~/Rlib_test', .libPaths())); testthat::test_file('tests/testthat/test-package-srcref-imputation.R')"The package's internals are implemented in C and invoked from thin R wrappers
via .Call. Building from source requires a C toolchain (Rtools on
Windows, the standard R CMD INSTALL toolchain on macOS / Linux). No
external libraries are needed.
This package was inspired in part by covr's parse-data handling approach.