Find unused/"orphaned" non-exported objects during package development

Viewed 50

I'm refactoring a package containing many non-exported functions and other non-exported objects. Is there a way to quickly identify all the "orphaned" objects that are defined but not exported or called anywhere else in the package, other than manually doing a full text search for the name each function in the package?

An additional complication is that some functions are called inside glue f-style strings, so there may be cases where functions are called that are not parsable as expressions (this is probably not a good design pattern).

But I would be satisfied with a method for just functions and other objects that never appear as normal expressions after being defined.

1 Answers

This will give you a partial answer (edited from the original):

pkg <- "testpkg"
library(pkg, character.only = TRUE)
ns <- getNamespace(pkg)
allnames <- ls(ns)
exports <- ls(paste0("package:", pkg))

nsInfo <- readRDS(system.file("Meta/nsInfo.rds", package = pkg))

if (!is.null(nsInfo$S3methods)) {
  S3methods <- with(nsInfo, paste(S3methods[,1], S3methods[,2], sep = "."))
} else
  S3methods <- NULL

locals <- setdiff(allnames, c(exports, S3methods))

used <- character()
newones <- c(exports, S3methods)
while (length(newones)) {
  mentioned <- unique(unlist(lapply(newones, function(n) {
      fun <- get(n, envir = ns)
      if (is.function(fun)) 
        codetools::findGlobals(fun)
        })))
  used <- c(used, newones)
  newones <- setdiff(intersect(mentioned, locals), used)
}
 
unused <- setdiff(locals, used)
unused

This still isn't quite right, but it should get you started. Some problems:

  • it assumes no functions are doing funny stuff like assign(), or fiddling with environments, etc.
  • it doesn't detect functions that are used entirely to build other objects in the package.
  • it doesn't detect weird S3 methods declared with a non-standard name.
Related