### * undoc
undoc <-
function(package, dir, lib.loc = NULL)
{
## Argument handling.
##
## Earlier versions used to give an error if there were no Rd
## objects. This is not right: if there is code or data but no
## documentation, everything is undocumented ...
##
if(!missing(package)) {
if(length(package) != 1)
stop("argument 'package' must be of length 1")
dir <- .find.package(package, lib.loc)
## Using package installed in @code{dir} ...
is_base <- package == "base"
all_doc_topics <- Rd_aliases(package, lib.loc = dirname(dir))
## Load package into code_env.
if(!is_base)
.load_package_quietly(package, lib.loc)
code_env <- .package_env(package)
code_objs <- ls(envir = code_env, all.names = TRUE)
pkgname <- package
}
else {
if(missing(dir))
stop("you must specify 'package' or 'dir'")
## Using sources from directory @code{dir} ...
if(!file_test("-d", dir))
stop(gettextf("directory '%s' does not exist", dir),
domain = NA)
else
dir <- file_path_as_absolute(dir)
pkgname <- basename(dir)
is_base <- pkgname == "base"
all_doc_topics <- Rd_aliases(dir = dir)
code_env <- new.env()
code_dir <- file.path(dir, "R")
if(file_test("-d", code_dir)) {
.source_assignments_in_code_dir(code_dir, code_env)
sys_data_file <- file.path(code_dir, "sysdata.rda")
if(file_test("-f", sys_data_file))
load(sys_data_file, code_env)
}
code_objs <- ls(envir = code_env, all.names = TRUE)
## Does the package have a NAMESPACE file? Note that when
## working on the sources we (currently?) cannot deal with the
## (experimental) alternative way of specifying the namespace.
if(file.exists(file.path(dir, "NAMESPACE"))) {
nsInfo <- parseNamespaceFile(basename(dir), dirname(dir))
## Look only at exported objects (and not declared S3
## methods).
OK <- code_objs[code_objs %in% nsInfo$exports]
for(p in nsInfo$exportPatterns)
OK <- c(OK, grep(p, code_objs, value = TRUE))
code_objs <- unique(OK)
}
}
data_objs <- character(0)
data_dir <- file.path(dir, "data")
if(file_test("-d", data_dir)) {
data_env <- new.env()
files <- list_files_with_type(data_dir, "data")
files <- unique(basename(file_path_sans_ext(files)))
##
## Argh. When working on the source directory of a package in a
## bundle, or a base package, we (currently?) cannot simply use
## data(). In these cases, we only have a 'DESCRIPTION.in'
## file. On the other hand, data() uses .find.package() to find
## the package paths from its 'package' and '.lib.loc'
## arguments, and .find.packages() is really for finding
## *installed* packages, and hence tests for the existence of a
## 'DESCRIPTION' file. As a last resort, use the fact that
## data() can be made to for look data sets in the 'data'
## subdirectory of the current working directory ...
package_name <- basename(dir)
libPath <- dirname(dir)
if(!file.exists(file.path(dir, "DESCRIPTION"))) {
## Hope that there is a 'DESCRIPTION.in', maybe we should
## check for this?
package_name <- character()
libPath <- NULL
owd <- getwd()
setwd(dir)
on.exit(setwd(owd))
}
##
for(f in files) {
##
## Non-standard evaluation for argument 'package' to data()
## gone in R 1.9.0.
.try_quietly(utils::data(list = f, package = package_name,
lib.loc = libPath, envir = data_env))
## (We use .try_quietly() because a .R data file using scan()
## to read in data from some other place may do this without
## 'quiet = TRUE', giving output which R CMD check would
## think to indicate a problem.)
##
new <- ls(envir = data_env, all.names = TRUE)
data_objs <- c(data_objs, new)
rm(list = new, envir = data_env)
}
}
## Undocumented objects?
if(!missing(package)
&& (length(code_objs) == 0)
&& (length(data_objs) == 0))
warning("neither code nor data objects found")
## When working on the sources, we will not get any code objects in
## case a package provides "just" S4 classes and methods.
if(!is_base) {
## Code objects in add-on packages with names starting with a
## dot are considered 'internal' (not user-level) by
## convention.
##
## Not clear whether everyone believes in this convention.
## We used to have
## allObjs[! allObjs %in% c(all_doc_topics,
## ".First.lib", ".Last.lib")]
## i.e., only exclude '.First.lib' and '.Last.lib'.
code_objs <- grep("^[^.].*", code_objs, value = TRUE)
## Note that this also allows us to get rid of S4 meta objects
## (with names starting with '.__C__' or '.__M__'; well, as long
## as there are none in base).
##
##
## Need to do something about S4 generic functions 'created' by
## setGeneric() or setMethod() on 'ordinary' functions.
## The test below exempts objects that are generic functions
## which are 'derived', either by importing from another
## package or from a default method.
## In the long run we need dynamic documentation.
if(.isMethodsDispatchOn()) {
code_objs <-
code_objs[sapply(code_objs, function(f) {
fdef <- get(f, envir = code_env)
if(methods::is(fdef, "genericFunction"))
fdef@package == pkgname
else
TRUE
}) == TRUE]
}
##
## Allow group generics to be undocumented other than in base.
## In particular, those from methods partially duplicate base
## and are documented in base's groupGenerics.Rd.
code_objs <-
code_objs %w/o% c("Arith", "Compare", "Complex", "Math",
"Math2", "Ops", "Summary")
}
##
## Currently, loading data from an R file via sys.source() puts
## .required into the load environment if the R code has a call to
## require().
data_objs <- data_objs %w/o% c(".required")
##
undoc_things <-
list("code objects" =
unique(code_objs %w/o% all_doc_topics),
"data sets" =
unique(data_objs %w/o% all_doc_topics))
if(.isMethodsDispatchOn()) {
## Undocumented S4 classes?
S4_classes <- methods::getClasses(code_env)
##
## There is no point in worrying about exportClasses directives
## in a NAMESPACE file when working on a package source dir, as
## we only source the assignments, and hence do not get any
## S4 classes or methods.
##
## The bad ones:
S4_classes <-
S4_classes[!sapply(S4_classes,
function(u) utils::topicName("class", u))
%in% all_doc_topics]
undoc_things <-
c(undoc_things, list("S4 classes" = unique(S4_classes)))
}
if(.isMethodsDispatchOn()) {
## Undocumented S4 methods?
##
## There is no point in worrying about exportMethods directives
## in a NAMESPACE file when working on a package source dir, as
## we only source the assignments, and hence do not get any
## S4 classes or methods.
##
methodsSignatures <- function(f) {
mlist <- methods::getMethodsMetaData(f, code_env)
meths <- methods::linearizeMlist(mlist, FALSE)
classes <- methods::slot(meths, "classes")
## Don't look for doc on a generated default method.
default <-
as.logical(lapply(classes,
function(x)
identical(all(x == "ANY"), TRUE)))
if(any(default)
&& methods::is(methods::finalDefaultMethod(mlist),
"derivedDefaultMethod")) {
classes <- classes[!default]
}
## Exclude methods inherited from the 'appropriate' parent
## environment.
##
## Keep this in sync with similar code in checkFF().
penv <- .Internal(getRegisteredNamespace(as.name(package)))
if(is.environment(penv))
penv <- parent.env(penv)
else
penv <- parent.env(code_env)
if((f %in% methods::getGenerics(penv))
&& !is.null(mlist_from_penv <-
methods::getMethodsMetaData(f, penv))) {
classes_from_penv <-
methods::slot(methods::linearizeMlist(mlist_from_penv),
"classes")
ind <- is.na(match(.make_signatures(classes),
.make_signatures(classes_from_penv)))
classes <- classes[ind]
}
##
sigs <- sapply(classes, paste, collapse = ",")
if(length(sigs))
paste(f, ",", sigs, sep = "")
else
character()
}
S4_methods <-
sapply(methods::getGenerics(code_env), methodsSignatures)
S4_methods <-
as.character(unlist(S4_methods, use.names = FALSE))
## The bad ones:
S4_methods <-
S4_methods[!sapply(S4_methods,
function(u)
utils::topicName("method", u))
%in% all_doc_topics]
undoc_things <-
c(undoc_things,
list("S4 methods" =
unique(sub("([^,]*),(.*)",
"generic '\\1' and siglist '\\2'",
S4_methods))))
}
class(undoc_things) <- "undoc"
undoc_things
}
print.undoc <-
function(x, ...)
{
for(i in which(sapply(x, length) > 0)) {
tag <- names(x)[i]
msg <- switch(tag,
"code objects" =
gettext("Undocumented code objects:"),
"data sets" =
gettext("Undocumented data sets:"),
"S4 classes" =
gettext("Undocumented S4 classes:"),
"S4 methods" =
gettext("Undocumented S4 methods:"),
gettextf("Undocumented %s:", tag))
writeLines(msg)
## We avoid markup for indicating S4 methods, hence need to
## special-case output for these ...
if(tag == "S4 methods")
writeLines(strwrap(x[[i]], indent = 2, exdent = 4))
else
.pretty_print(x[[i]])
}
invisible(x)
}
### * codoc
codoc <-
function(package, dir, lib.loc = NULL,
use.values = NULL, verbose = getOption("verbose"))
{
##
## Improvements worth considering:
## * Parallelize the actual checking (it is not necessary to loop
## over the Rd files);
##
has_namespace <- FALSE
## Argument handling.
if(!missing(package)) {
if(length(package) != 1)
stop("argument 'package' must be of length 1")
dir <- .find.package(package, lib.loc)
## Using package installed in @code{dir} ...
code_dir <- file.path(dir, "R")
if(!file_test("-d", code_dir))
stop(gettextf("directory '%s' does not contain R code",
dir),
domain = NA)
docs_dir <- file.path(dir, "man")
if(!file_test("-d", docs_dir))
stop(gettextf("directory '%s' does not contain Rd sources",
dir),
domain = NA)
is_base <- basename(dir) == "base"
## Load package into code_env.
if(!is_base)
.load_package_quietly(package, lib.loc)
code_env <- .package_env(package)
objects_in_code <- objects(envir = code_env, all.names = TRUE)
## Does the package have a namespace?
if(packageHasNamespace(package, dirname(dir))) {
has_namespace <- TRUE
ns_env <- asNamespace(package)
S3Table <- get(".__S3MethodsTable__.", envir = ns_env)
functions_in_S3Table <- ls(S3Table, all.names = TRUE)
objects_in_ns <-
(objects(envir = ns_env, all.names = TRUE) %w/o%
c(".__NAMESPACE__.", ".__S3MethodsTable__."))
objects_in_code_or_namespace <-
unique(c(objects_in_code, objects_in_ns))
objects_in_ns <- objects_in_ns %w/o% objects_in_code
}
else
objects_in_code_or_namespace <- objects_in_code
package_name <- package
}
else {
if(missing(dir))
stop("you must specify 'package' or 'dir'")
## Using sources from directory @code{dir} ...
if(!file_test("-d", dir))
stop(gettextf("directory '%s' does not exist", dir),
domain = NA)
else
dir <- file_path_as_absolute(dir)
code_dir <- file.path(dir, "R")
if(!file_test("-d", code_dir))
stop(gettextf("directory '%s' does not contain R code",
dir),
domain = NA)
docs_dir <- file.path(dir, "man")
if(!file_test("-d", docs_dir))
stop(gettextf("directory '%s' does not contain Rd sources",
dir),
domain = NA)
package_name <- basename(dir)
is_base <- package_name == "base"
code_env <- new.env()
.source_assignments_in_code_dir(code_dir, code_env)
sys_data_file <- file.path(code_dir, "sysdata.rda")
if(file_test("-f", sys_data_file)) load(sys_data_file, code_env)
objects_in_code <- objects(envir = code_env, all.names = TRUE)
objects_in_code_or_namespace <- objects_in_code
## Does the package have a NAMESPACE file? Note that when
## working on the sources we (currently?) cannot deal with the
## (experimental) alternative way of specifying the namespace.
## Also, do not attempt to find S3 methods.
if(file.exists(file.path(dir, "NAMESPACE"))) {
has_namespace <- TRUE
objects_in_ns <- objects_in_code
functions_in_S3Table <- character(0)
ns_env <- code_env
nsInfo <- parseNamespaceFile(basename(dir), dirname(dir))
## Look only at exported objects.
OK <- objects_in_code[objects_in_code %in% nsInfo$exports]
for(p in nsInfo$exportPatterns)
OK <- c(OK, grep(p, objects_in_code, value = TRUE))
objects_in_code <- unique(OK)
}
}
## Find the function objects to work on.
functions_in_code <-
objects_in_code[sapply(objects_in_code,
function(f) {
f <- get(f, envir = code_env)
is.function(f) && (length(formals(f)) > 0)
}) == TRUE]
##
## Sourcing all R code files in the package is a problem for base,
## where this misses the .Primitive functions. Hence, when checking
## base for objects shown in \usage but missing from the code, we
## get the primitive functions from the version of R we are using.
## Maybe one day we will have R code for the primitives as well ...
if(is_base) {
objects_in_base <-
objects(envir = baseenv(), all.names = TRUE)
objects_in_code <-
c(objects_in_code,
objects_in_base[sapply(objects_in_base,
.is_primitive,
baseenv())],
c(".First.lib", ".Last.lib", ".Random.seed",
".onLoad", ".onAttach", ".onUnload"))
objects_in_code_or_namespace <- objects_in_code
}
##
## Build a list with the formals of the functions in the code
## indexed by the names of the functions.
function_args_in_code <-
lapply(functions_in_code,
function(f) formals(get(f, envir = code_env)))
names(function_args_in_code) <- functions_in_code
if(has_namespace) {
functions_in_ns <-
objects_in_ns[sapply(objects_in_ns,
function(f) {
f <- get(f, envir = ns_env)
is.function(f) && (length(formals(f)) > 0)
}) == TRUE]
function_args_in_ns <-
lapply(functions_in_ns,
function(f) formals(get(f, envir = ns_env)))
names(function_args_in_ns) <- functions_in_ns
function_args_in_S3Table <-
lapply(functions_in_S3Table,
function(f) formals(get(f, envir = S3Table)))
names(function_args_in_S3Table) <- functions_in_S3Table
tmp <- c(function_args_in_code, function_args_in_S3Table,
function_args_in_ns)
keep <- !duplicated(names(tmp))
function_args_in_code <- tmp[keep]
functions_in_code <- names(function_args_in_code)
}
if(.isMethodsDispatchOn()) {
##
## There is no point in worrying about exportMethods directives
## in a NAMESPACE file when working on a package source dir, as
## we only source the assignments, and hence do not get any
## S4 classes or methods.
##
lapply(methods::getGenerics(code_env),
function(f) {
meths <-
methods::linearizeMlist(methods::getMethodsMetaData(f, code_env))
sigs <- sapply(methods::slot(meths, "classes"),
paste, collapse = ",")
if(!length(sigs)) return()
args <- lapply(methods::slot(meths, "methods"),
formals)
names(args) <-
paste("\\S4method{", f, "}{", sigs, "}",
sep = "")
function_args_in_code <<- c(function_args_in_code, args)
})
}
check_codoc <- function(fName, ffd) {
## Compare the formals of the function in the code named 'fName'
## and formals 'ffd' obtained from the documentation.
ffc <- function_args_in_code[[fName]]
if(identical(use.values, FALSE)) {
ffc <- names(ffc)
ffd <- names(ffd)
ok <- identical(ffc, ffd)
} else {
if(!identical(names(ffc), names(ffd)))
ok <- FALSE
else {
vffc <- as.character(ffc) # values
vffd <- as.character(ffd) # values
if(!identical(use.values, TRUE)) {
ind <- nchar(as.character(ffd)) > 0
vffc <- vffc[ind]
vffd <- vffd[ind]
}
ok <- identical(vffc, vffd)
}
}
if(ok)
NULL
else
list(list(name = fName, code = ffc, docs = ffd))
}
db <- if(!missing(package))
Rd_db(package, lib.loc = dirname(dir))
else
Rd_db(dir = dir)
db <- lapply(db, function(f) paste(Rd_pp(f), collapse = "\n"))
names(db) <- db_names <- .get_Rd_names_from_Rd_db(db)
## pkg-defunct.Rd is not expected to list arguments
ind <- db_names %in% paste(package_name, "defunct", sep="-")
db <- db[!ind]
db_names <- db_names[!ind]
db_usage_texts <-
.apply_Rd_filter_to_Rd_db(db, get_Rd_section, "usage")
db_synopses <-
.apply_Rd_filter_to_Rd_db(db, get_Rd_section, "synopsis")
ind <- sapply(db_synopses, length) > 0
db_usage_texts[ind] <- db_synopses[ind]
with_synopsis <- as.character(db_names[ind])
db_usages <- lapply(db_usage_texts, .parse_usage_as_much_as_possible)
ind <- sapply(db_usages,
function(x) !is.null(attr(x, "bad_lines")))
bad_lines <- lapply(db_usages[ind], attr, "bad_lines")
##
## Currently, there is no useful markup for S3 Ops group methods
## and S3 methods for subscripting and subassigning. Hence, we
## cannot reliably distinguish between usage for the generic and
## that of a method ...
functions_to_be_ignored <-
c(.functions_to_be_ignored_from_usage(basename(dir)),
.functions_with_no_useful_S3_method_markup())
##
bad_doc_objects <- list()
functions_in_usages <- character()
variables_in_usages <- character()
data_sets_in_usages <- character()
functions_in_usages_not_in_code <- list()
for(docObj in db_names) {
exprs <- db_usages[[docObj]]
if(!length(exprs)) next
## Get variable names and data set usages first, mostly for
## curiosity.
ind <- ! sapply(exprs, is.call)
if(any(ind)) {
variables_in_usages <-
c(variables_in_usages,
sapply(exprs[ind], deparse))
exprs <- exprs[!ind]
}
ind <- as.logical(sapply(exprs,
function(e)
(length(e) == 2)
&& e[[1]] == as.symbol("data")))
if(any(ind)) {
data_sets_in_usages <-
c(data_sets_in_usages,
sapply(exprs[ind], function(e) as.character(e[[2]])))
exprs <- exprs[!ind]
}
functions <- sapply(exprs, function(e) as.character(e[[1]]))
functions <- .transform_S3_method_markup(as.character(functions))
ind <- (! functions %in% functions_to_be_ignored
& functions %in% functions_in_code)
bad_functions <-
mapply(functions[ind],
exprs[ind],
FUN = function(x, y)
check_codoc(x, as.pairlist(as.alist.call(y[-1]))),
SIMPLIFY = FALSE)
## Replacement functions.
ind <- as.logical(sapply(exprs,
.is_call_from_replacement_function_usage))
if(any(ind)) {
exprs <- exprs[ind]
replace_funs <-
paste(sapply(exprs,
function(e) as.character(e[[2]][[1]])),
"<-",
sep = "")
replace_funs <- .transform_S3_method_markup(replace_funs)
functions <- c(functions, replace_funs)
ind <- (replace_funs %in% functions_in_code)
if(any(ind)) {
bad_replace_funs <-
mapply(replace_funs[ind],
exprs[ind],
FUN = function(x, y)
check_codoc(x,
as.pairlist(c(as.alist.call(y[[2]][-1]),
as.alist.symbol(y[[3]])))),
SIMPLIFY = FALSE)
bad_functions <-
c(bad_functions, bad_replace_funs)
}
}
bad_functions <- do.call("c", bad_functions)
if(length(bad_functions) > 0)
bad_doc_objects[[docObj]] <- bad_functions
## Determine functions with a \usage entry in the documentation
## but 'missing from the code'. If a package has a namespace, we
## really need to look at all objects in the namespace (hence
## 'objects_in_code_or_namespace'), as one can access the internal
## symbols via ':::' and hence package developers might want to
## provide function usages for some of the internal functions.
##
## We may still have \S4method{}{} entries in functions, which
## cannot have a corresponding object in the code. Hence, we
## remove these function entries, but should really do better,
## by comparing the explicit \usage entries for S4 methods to
## what is actually in the code. We most likely also should do
## something similar for S3 methods.
ind <- grep(.S4_method_markup_regexp, functions)
if(any(ind))
functions <- functions[!ind]
##
bad_functions <-
functions %w/o% c(objects_in_code_or_namespace,
functions_to_be_ignored)
if(length(bad_functions) > 0)
functions_in_usages_not_in_code[[docObj]] <- bad_functions
functions_in_usages <- c(functions_in_usages, functions)
}
## Determine (function) objects in the code without a \usage entry.
## Of course, these could still be 'documented' via \alias.
##
## Older versions only printed this information without returning it
## (in case 'verbose' was true). We now add this as an attribute to
## the bad_doc_objects returned.
##
objects_in_code_not_in_usages <-
objects_in_code %w/o% c(functions_in_usages, variables_in_usages)
functions_in_code_not_in_usages <-
functions_in_code[functions_in_code %in% objects_in_code_not_in_usages]
## (Note that 'functions_in_code' does not necessarily contain all
## (exported) functions in the package.)
attr(bad_doc_objects, "objects_in_code_not_in_usages") <-
objects_in_code_not_in_usages
attr(bad_doc_objects, "functions_in_code_not_in_usages") <-
functions_in_code_not_in_usages
attr(bad_doc_objects, "functions_in_usages_not_in_code") <-
functions_in_usages_not_in_code
attr(bad_doc_objects, "function_args_in_code") <-
function_args_in_code
attr(bad_doc_objects, "has_namespace") <- has_namespace
attr(bad_doc_objects, "with_synopsis") <- with_synopsis
attr(bad_doc_objects, "bad_lines") <- bad_lines
class(bad_doc_objects) <- "codoc"
bad_doc_objects
}
print.codoc <-
function(x, ...)
{
## In general, functions in the code which only have an \alias but
## no \usage entry are not necessarily a problem---they might be
## mentioned in other parts of the Rd object documenting them, or be
## 'internal'. However, if a package has a namespace (and this was
## used in the codoc() computations), then clearly all *exported*
## functions should have \usage entries.
##
## Things are not quite that simple.
## E.g., for generic functions with just a default and a formula
## method we typically do not have \usage for the generic itself.
## (This will change now with the new \method{}{} transformation.)
## Also, earlier versions of codoc() based on the defunct Perl code
## in extract-usage.pl (now removed) only dealt with the *functions*
## so all variables would come out as 'without usage information' ...
## As we can always access the information via
## attr(codoc("foo"), "objects_in_code_not_in_usages")
## disable reporting this for the time being ...
##
## objects_in_code_not_in_usages <-
## attr(x, "objects_in_code_not_in_usages")
## if(length(objects_in_code_not_in_usages)
## && identical(TRUE, attr(x, "has_namespace"))) {
## if(length(objects_in_code_not_in_usages)) {
## writeLines("Exported objects without usage information:")
## .pretty_print(objects_in_code_not_in_usages)
## writeLines("")
## }
## }
##
## Hmm. But why not mention the exported *functions* without \usage
## information? Note that currently there is no useful markup for
## S3 Ops group methods and S3 methods for subscripting and
## subassigning, so the corresponding generics and methods cannot
## reliably be distinguished, and hence would need to be excluded
## here as well.
##
## functions_in_code_not_in_usages <-
## attr(x, "functions_in_code_not_in_usages")
## if(length(functions_in_code_not_in_usages)
## && identical(TRUE, attr(x, "has_namespace"))) {
## if(length(functions_in_code_not_in_usages)) {
## writeLines("Exported functions without usage information:")
## .pretty_print(functions_in_code_not_in_usages)
## writeLines("")
## }
## }
##
##
functions_in_usages_not_in_code <-
attr(x, "functions_in_usages_not_in_code")
if(length(functions_in_usages_not_in_code) > 0) {
for(fname in names(functions_in_usages_not_in_code)) {
writeLines(gettextf("Functions/methods with usage in documentation object '%s' but not in code:",
fname))
.pretty_print(unique(functions_in_usages_not_in_code[[fname]]))
writeLines("")
}
}
if(length(x) == 0)
return(invisible(x))
has_only_names <- is.character(x[[1]][[1]][["code"]])
format_args <- function(s) {
if(length(s) == 0)
"function()"
else if(has_only_names)
paste("function(", paste(s, collapse = ", "), ")", sep = "")
else {
s <- paste(deparse(s), collapse = "")
s <- gsub(" = \([,\\)]\)", "\\1", s)
gsub("^list", "function", s)
}
}
summarize_mismatches_in_names <- function(nfc, nfd) {
if(length(nms <- nfc %w/o% nfd))
writeLines(c(gettext(" Argument names in code not in docs:"),
strwrap(paste(nms, collapse = " "),
indent = 4, exdent = 4)))
if(length(nms <- nfd %w/o% nfc))
writeLines(c(gettext(" Argument names in docs not in code:"),
strwrap(paste(nms, collapse = " "),
indent = 4, exdent = 4)))
len <- min(length(nfc), length(nfd))
if(len) {
len <- seq_len(len)
nfc <- nfc[len]
nfd <- nfd[len]
ind <- which(nfc != nfd)
len <- length(ind)
if(len) {
if(len > 3) {
writeLines(gettext(" Mismatches in argument names (first 3):"))
ind <- ind[1 : 3]
} else {
writeLines(gettext(" Mismatches in argument names:"))
}
for(i in ind) {
writeLines(sprintf(" Position: %d Code: %s Docs: %s",
i, nfc[i], nfd[i]))
}
}
}
}
summarize_mismatches_in_values <- function(ffc, ffd) {
## Be nice, and match arguments by names first.
nms <- intersect(names(ffc), names(ffd))
vffc <- as.character(ffc[nms])
vffd <- as.character(ffd[nms])
ind <- which(vffc != vffd)
len <- length(ind)
if(len) {
if(len > 3) {
writeLines(gettext(" Mismatches in argument default values (first 3):"))
ind <- ind[1 : 3]
} else {
writeLines(gettext(" Mismatches in argument default values:"))
}
for(i in ind) {
writeLines(sprintf(" Name: %s Code: %s Docs: %s",
nms[i], vffc[i], vffd[i]))
}
}
}
summarize_mismatches <- function(ffc, ffd) {
if(has_only_names)
summarize_mismatches_in_names(ffc, ffd)
else {
summarize_mismatches_in_names(names(ffc), names(ffd))
summarize_mismatches_in_values(ffc, ffd)
}
}
for(fname in names(x)) {
writeLines(gettextf("Codoc mismatches from documentation object '%s':",
fname))
xfname <- x[[fname]]
for(i in seq_along(xfname)) {
ffc <- xfname[[i]][["code"]]
ffd <- xfname[[i]][["docs"]]
writeLines(c(xfname[[i]][["name"]],
strwrap(gettextf("Code: %s", format_args(ffc)),
indent = 2, exdent = 17),
strwrap(gettextf("Docs: %s", format_args(ffd)),
indent = 2, exdent = 17)))
summarize_mismatches(ffc, ffd)
}
writeLines("")
}
invisible(x)
}
### * codocClasses
codocClasses <-
function(package, lib.loc = NULL)
{
## Compare the 'structure' of S4 classes in an installed package
## between code and documentation.
## Currently, only compares the slot names.
##
## This is patterned after the current codoc().
## It would be useful to return the whole information on class slot
## names found in the code and matching documentation (rather than
## just the ones with mismatches).
## Currently, we only return the names of all classes checked.
##
bad_Rd_objects <- list()
class(bad_Rd_objects) <- "codocClasses"
## Argument handling.
if(length(package) != 1)
stop("argument 'package' must be of length 1")
dir <- .find.package(package, lib.loc)
if(!file_test("-d", file.path(dir, "R")))
stop(gettextf("directory '%s' does not contain R code", dir),
domain = NA)
if(!file_test("-d", file.path(dir, "man")))
stop(gettextf("directory '%s' does not contain Rd sources",
dir),
domain = NA)
is_base <- basename(dir) == "base"
## Load package into code_env.
if(!is_base)
.load_package_quietly(package, lib.loc)
code_env <- .package_env(package)
if(!.isMethodsDispatchOn())
return(bad_Rd_objects)
S4_classes <- methods::getClasses(code_env)
if(!length(S4_classes)) return(bad_Rd_objects)
## Build Rd data base.
db <- Rd_db(package, lib.loc = dirname(dir))
db <- lapply(db, function(f) Rd_pp(f))
## Need some heuristics now. When does an Rd object document just
## one S4 class so that we can compare (at least) the slot names?
## Try the following:
## * \docType{} identical to "class";
## * just one \alias{} (could also check whether it ends in
## "-class");
## * a non-empty user-defined section 'Slots'.
## As going through the db to extract sections can take some time,
## we do the vectorized metadata computations first, and try to
## subscript whenever possible.
aliases <- lapply(db, .get_Rd_metadata_from_Rd_lines, "alias")
idx <- (sapply(aliases, length) == 1)
if(!any(idx)) return(bad_Rd_objects)
db <- db[idx]; aliases <- aliases[idx]
idx <- sapply(lapply(db, .get_Rd_metadata_from_Rd_lines, "docType"),
identical, "class")
if(!any(idx)) return(bad_Rd_objects)
db <- db[idx]; aliases <- aliases[idx]
## Now collapse.
db <- lapply(db, paste, collapse = "\n")
Rd_slots <-
.apply_Rd_filter_to_Rd_db(db, get_Rd_section, "Slots", FALSE)
idx <- !sapply(Rd_slots, identical, character())
if(!any(idx)) return(bad_Rd_objects)
db <- db[idx]
aliases <- unlist(aliases[idx])
Rd_slots <- Rd_slots[idx]
names(db) <- .get_Rd_names_from_Rd_db(db)
.get_slot_names_from_slot_section_text <- function(txt) {
## Get \describe (inside user-defined section 'Slots')
txt <- unlist(sapply(txt, get_Rd_section, "describe"))
## Suppose this worked ...
## Get the \items inside \describe
txt <- unlist(sapply(txt, get_Rd_items))
if(!length(txt)) return(character())
## And now strip enclosing '\code{...}:'
txt <- gsub("\\\\code\\{([^\}]*)\\}:?", "\\1", as.character(txt))
txt <- unlist(strsplit(txt, ", *"))
txt <- sub("^[[:space:]]+", "", txt)
txt <- sub("[[:space:]]+$", "", txt)
txt
}
S4_classes_checked <- character()
for(cl in S4_classes) {
idx <- which(utils::topicName("class", cl) == aliases)
if(length(idx) == 1) {
## Add sanity checking later ...
S4_classes_checked <- c(S4_classes_checked, cl)
slots_in_code <-
sort(names(methods::slot(methods::getClass(cl, where =
code_env),
"slots")))
slots_in_docs <-
sort(.get_slot_names_from_slot_section_text(Rd_slots[[idx]]))
if(!identical(slots_in_code, slots_in_docs)) {
bad_Rd_objects[[names(db)[idx]]] <-
list(name = cl,
code = slots_in_code,
docs = slots_in_docs)
}
}
}
attr(bad_Rd_objects, "S4_classes_checked") <-
as.character(S4_classes_checked)
bad_Rd_objects
}
print.codocClasses <-
function(x, ...)
{
if (length(x) == 0)
return(invisible(x))
format_args <- function(s) paste(s, collapse = " ")
for (docObj in names(x)) {
writeLines(gettextf("S4 class codoc mismatches from documentation object '%s':",
docObj))
docObj <- x[[docObj]]
writeLines(c(gettextf("Slots for class '%s'", docObj[["name"]]),
strwrap(gettextf("Code: %s",
format_args(docObj[["code"]])),
indent = 2, exdent = 8),
strwrap(gettextf("Docs: %s",
format_args(docObj[["docs"]])),
indent = 2, exdent = 8)))
writeLines("")
}
invisible(x)
}
### * codocData
codocData <-
function(package, lib.loc = NULL)
{
## Compare the 'structure' of 'data' objects (variables or data
## sets) in an installed package between code and documentation.
## Currently, only compares the variable names of data frames found.
##
## This is patterned after the current codoc().
## It would be useful to return the whole information on data frame
## variable names found in the code and matching documentation
## (rather than just the ones with mismatches).
## Currently, we only return the names of all data frames checked.
##
bad_Rd_objects <- list()
class(bad_Rd_objects) <- "codocData"
## Argument handling.
if(length(package) != 1)
stop("argument 'package' must be of length 1")
dir <- .find.package(package, lib.loc)
if(!file_test("-d", file.path(dir, "man")))
stop(gettextf("directory '%s' does not contain Rd sources", dir),
domain = NA)
is_base <- basename(dir) == "base"
has_namespace <- !is_base && packageHasNamespace(package, dirname(dir))
## Load package into code_env.
if(!is_base)
.load_package_quietly(package, lib.loc)
code_env <- .package_env(package)
if(has_namespace) ns_env <- asNamespace(package)
## Could check here whether the package has any variables or data
## sets (and return if not).
## Build Rd data base.
db <- Rd_db(package, lib.loc = dirname(dir))
db <- lapply(db, function(f) Rd_pp(f))
## Need some heuristics now. When does an Rd object document a
## data.frame (could add support for other classes later) variable
## or data set so that we can compare (at least) the names of the
## variables in the data frame? Try the following:
## * just one \alias{};
## * if documentation was generated via prompt, there is a \format
## section starting with 'A data frame with' (but many existing Rd
## files instead have 'This data frame contains' and containing
## one or more \describe sections inside.
## As going through the db to extract sections can take some time,
## we do the vectorized metadata computations first, and try to
## subscript whenever possible.
aliases <- lapply(db, .get_Rd_metadata_from_Rd_lines, "alias")
idx <- sapply(aliases, length) == 1
if(!any(idx)) return(bad_Rd_objects)
db <- db[idx]; aliases <- aliases[idx]
## Now collapse.
db <- lapply(db, paste, collapse = "\n")
names(db) <- .get_Rd_names_from_Rd_db(db)
.get_data_frame_var_names_from_Rd_text <- function(txt) {
txt <- get_Rd_section(txt, "format")
## Was there just one \format section?
if(length(txt) != 1) return(character())
## What did it start with?
if(!length(grep("^[ \n\t]*(A|This) data frame", txt)))
return(character())
## Get \describe inside \format
txt <- get_Rd_section(txt, "describe")
## Suppose this worked ...
## Get the \items inside \describe
txt <- unlist(sapply(txt, get_Rd_items))
if(!length(txt)) return(character())
txt <- gsub("(.*):$", "\\1", as.character(txt))
txt <- gsub("\\\\code\\{(.*)\\}:?", "\\1", txt)
## Argh. Of course, variable names can have a '_', which needs
## to be escaped if not in \code{}, and the prompt() default is
## not to put variable names inside \code{}.
txt <- gsub("\\\\_", "_", txt)
txt <- unlist(strsplit(txt, ", *"))
txt <- sub("^[[:space:]]+", "", txt)
txt <- sub("[[:space:]]+$", "", txt)
txt
}
Rd_var_names <-
.apply_Rd_filter_to_Rd_db(db, .get_data_frame_var_names_from_Rd_text)
idx <- (sapply(Rd_var_names, length) > 0)
if(!length(idx)) return(bad_Rd_objects)
aliases <- unlist(aliases[idx])
Rd_var_names <- Rd_var_names[idx]
db_names <- names(db)[idx]
data_env <- new.env()
data_dir <- file.path(dir, "data")
## with lazy data we have data() but don't need to use it.
hasData <- file_test("-d", data_dir) &&
!file_test("-f", file.path(data_dir, "Rdata.rdb"))
data_exts <- .make_file_exts("data")
## Now go through the aliases.
data_frames_checked <- character()
for(i in seq_along(aliases)) {
## Store the documented variable names.
var_names_in_docs <- sort(Rd_var_names[[i]])
## Try finding the variable or data set given by the alias.
al <- aliases[i]
if(exists(al, envir = code_env, mode = "list",
inherits = FALSE)) {
al <- get(al, envir = code_env, mode = "list")
} else if(has_namespace && exists(al, envir = ns_env, mode = "list",
inherits = FALSE)) {
al <- get(al, envir = ns_env, mode = "list")
} else if(hasData) {
## Should be a data set.
if(!length(dir(data_dir)
%in% paste(al, data_exts, sep = "."))) {
next # What the hell did we pick up?
}
## Try loading the data set into data_env.
utils::data(list = al, envir = data_env)
if(exists(al, envir = data_env, mode = "list",
inherits = FALSE)) {
al <- get(al, envir = data_env, mode = "list")
}
## And clean up data_env.
rm(list = ls(envir = data_env, all.names = TRUE),
envir = data_env)
}
if(!is.data.frame(al)) next
## Now we should be ready:
data_frames_checked <- c(data_frames_checked, aliases[i])
var_names_in_code <- sort(names(al))
if(!identical(var_names_in_code, var_names_in_docs))
bad_Rd_objects[[db_names[i]]] <-
list(name = aliases[i],
code = var_names_in_code,
docs = var_names_in_docs)
}
attr(bad_Rd_objects, "data_frames_checked") <-
as.character(data_frames_checked)
bad_Rd_objects
}
print.codocData <-
function(x, ...)
{
format_args <- function(s) paste(s, collapse = " ")
for(docObj in names(x)) {
writeLines(gettextf("Data codoc mismatches from documentation object '%s':",
docObj))
docObj <- x[[docObj]]
writeLines(c(gettextf("Variables in data frame '%s'",
docObj[["name"]]),
strwrap(gettextf("Code: %s",
format_args(docObj[["code"]])),
indent = 2, exdent = 8),
strwrap(gettextf("Docs: %s",
format_args(docObj[["docs"]])),
indent = 2, exdent = 8)))
writeLines("")
}
invisible(x)
}
### * checkDocFiles
checkDocFiles <-
function(package, dir, lib.loc = NULL)
{
## Argument handling.
if(!missing(package)) {
if(length(package) != 1)
stop("argument 'package' must be of length 1")
dir <- .find.package(package, lib.loc)
## Using package installed in @code{dir} ...
}
else {
if(missing(dir))
stop("you must specify 'package' or 'dir'")
## Using sources from directory @code{dir} ...
if(!file_test("-d", dir))
stop(gettextf("directory '%s' does not exist", dir),
domain = NA)
else
dir <- file_path_as_absolute(dir)
}
docs_dir <- file.path(dir, "man")
if(!file_test("-d", docs_dir))
stop(gettextf("directory '%s' does not contain Rd sources",
dir),
domain = NA)
db <- if(!missing(package))
Rd_db(package, lib.loc = dirname(dir))
else
Rd_db(dir = dir)
db <- lapply(db, function(f) Rd_pp(f))
## Do vectorized computations for metadata first.
db_aliases <- lapply(db, .get_Rd_metadata_from_Rd_lines, "alias")
db_keywords <- lapply(db, .get_Rd_metadata_from_Rd_lines, "keyword")
## Now collapse.
db <- lapply(db, paste, collapse = "\n")
db_names <- .get_Rd_names_from_Rd_db(db)
names(db) <- names(db_aliases) <- db_names
db_usage_texts <-
.apply_Rd_filter_to_Rd_db(db, get_Rd_section, "usage")
db_usages <- lapply(db_usage_texts, .parse_usage_as_much_as_possible)
ind <- as.logical(sapply(db_usages,
function(x) !is.null(attr(x, "bad_lines"))))
bad_lines <- lapply(db_usages[ind], attr, "bad_lines")
## Exclude internal objects from further computations.
ind <- sapply(db_keywords,
function(x) any(grep("^ *internal *$", x)))
if(any(ind)) { # exclude them
db <- db[!ind]
db_names <- db_names[!ind]
db_aliases <- db_aliases[!ind]
}
db_argument_names <-
.apply_Rd_filter_to_Rd_db(db, .get_Rd_argument_names)
functions_to_be_ignored <-
.functions_to_be_ignored_from_usage(basename(dir))
bad_doc_objects <- list()
for(docObj in db_names) {
exprs <- db_usages[[docObj]]
if(!length(exprs)) next
aliases <- db_aliases[[docObj]]
arg_names_in_arg_list <- db_argument_names[[docObj]]
## Determine function names ('functions') and corresponding
## arguments ('arg_names_in_usage') in the \usage. Note how we
## try to deal with data set documentation.
ind <- as.logical(sapply(exprs,
function(e)
((length(e) > 1) &&
!((length(e) == 2)
&& e[[1]] == as.symbol("data")))))
exprs <- exprs[ind]
## Ordinary functions.
functions <- as.character(sapply(exprs,
function(e)
as.character(e[[1]])))
## (Note that as.character(sapply(exprs, "[[", 1)) does not do
## what we want due to backquotifying.)
ind <- ! functions %in% functions_to_be_ignored
functions <- functions[ind]
arg_names_in_usage <-
unlist(sapply(exprs[ind],
function(e) .arg_names_from_call(e[-1])))
## Replacement functions.
ind <- as.logical(sapply(exprs,
.is_call_from_replacement_function_usage))
if(any(ind)) {
replace_funs <-
paste(sapply(exprs[ind],
function(e) as.character(e[[2]][[1]])),
"<-",
sep = "")
functions <- c(functions, replace_funs)
arg_names_in_usage <-
c(arg_names_in_usage,
unlist(sapply(exprs[ind],
function(e)
c(.arg_names_from_call(e[[2]][-1]),
.arg_names_from_call(e[[3]])))))
}
## And finally transform the S3 \method{}{} markup into the
## usual function names ...
##
## If we were really picky, we would worry about possible
## namespace renaming.
functions <- .transform_S3_method_markup(functions)
##
## Now analyze what we found.
arg_names_in_usage_missing_in_arg_list <-
arg_names_in_usage %w/o% arg_names_in_arg_list
arg_names_in_arg_list_missing_in_usage <-
arg_names_in_arg_list %w/o% arg_names_in_usage
if(length(arg_names_in_arg_list_missing_in_usage) > 0) {
usage_text <- db_usage_texts[[docObj]]
bad_args <- character()
## In the case of 'over-documented' arguments, try to be
## defensive and reduce to arguments which either are not
## syntactically valid names of do not match the \usage text
## (modulo word boundaries).
bad <- regexpr("^[[:alnum:]._]+$",
arg_names_in_arg_list_missing_in_usage) == -1
if(any(bad)) {
bad_args <- arg_names_in_arg_list_missing_in_usage[bad]
arg_names_in_arg_list_missing_in_usage <-
arg_names_in_arg_list_missing_in_usage[!bad]
}
bad <- sapply(arg_names_in_arg_list_missing_in_usage,
function(x)
regexpr(paste("\\b", x, "\\b", sep = ""),
usage_text) == -1)
arg_names_in_arg_list_missing_in_usage <-
c(bad_args,
arg_names_in_arg_list_missing_in_usage[as.logical(bad)])
## Note that the fact that we can parse the raw \usage does
## not imply that over-documented arguments are a problem:
## this works for Rd files documenting e.g. shell utilities
## but fails for files with special syntax (Extract.Rd).
}
## Also test whether the objects we found from the \usage all
## have aliases, provided that there is no alias which ends in
## '-deprecated' (see e.g. base-deprecated.Rd).
if(!any(grep("-deprecated$", aliases))) {
## Currently, there is no useful markup for S3 Ops group
## methods and S3 methods for subscripting and subassigning,
## so the corresponding generics and methods need to be
## excluded from this test (e.g., the usage for '+' in
## 'DateTimeClasses.Rd' ...).
functions <-
functions %w/o% .functions_with_no_useful_S3_method_markup()
## Argh. There are good reasons for keeping \S4method{}{}
## as is, but of course this is not what the aliases use ...
##
## Should maybe use topicName(), but in any case, we should
## have functions for converting between the two forms, see
## also the code for undoc().
aliases <- sub("([^,]+),(.+)-method$",
"\\\\S4method{\\1}{\\2}",
aliases)
##
aliases <- gsub("\\\\%", "%", aliases)
functions_not_in_aliases <- functions %w/o% aliases
}
else
functions_not_in_aliases <- character()
if((length(arg_names_in_usage_missing_in_arg_list) > 0)
|| any(duplicated(arg_names_in_arg_list))
|| (length(arg_names_in_arg_list_missing_in_usage) > 0)
|| (length(functions_not_in_aliases) > 0))
bad_doc_objects[[docObj]] <-
list(missing = arg_names_in_usage_missing_in_arg_list,
duplicated =
arg_names_in_arg_list[duplicated(arg_names_in_arg_list)],
overdoc = arg_names_in_arg_list_missing_in_usage,
unaliased = functions_not_in_aliases)
}
class(bad_doc_objects) <- "checkDocFiles"
attr(bad_doc_objects, "bad_lines") <- bad_lines
bad_doc_objects
}
print.checkDocFiles <-
function(x, ...)
{
for(doc_obj in names(x)) {
arg_names_in_usage_missing_in_arg_list <- x[[doc_obj]][["missing"]]
if(length(arg_names_in_usage_missing_in_arg_list) > 0) {
writeLines(gettextf("Undocumented arguments in documentation object '%s'",
doc_obj))
.pretty_print(unique(arg_names_in_usage_missing_in_arg_list))
}
duplicated_args_in_arg_list <- x[[doc_obj]][["duplicated"]]
if(length(duplicated_args_in_arg_list) > 0) {
writeLines(gettextf("Duplicated \\argument entries in documentation object '%s':",
doc_obj))
.pretty_print(duplicated_args_in_arg_list)
}
arg_names_in_arg_list_missing_in_usage <- x[[doc_obj]][["overdoc"]]
if(length(arg_names_in_arg_list_missing_in_usage) > 0) {
writeLines(gettextf("Documented arguments not in \\usage in documentation object '%s':",
doc_obj))
.pretty_print(unique(arg_names_in_arg_list_missing_in_usage))
}
functions_not_in_aliases <- x[[doc_obj]][["unaliased"]]
if(length(functions_not_in_aliases) > 0) {
writeLines(gettextf("Objects in \\usage without \\alias in documentation object '%s':",
doc_obj))
.pretty_print(unique(functions_not_in_aliases))
}
writeLines("")
}
if(!identical(as.logical(Sys.getenv("_R_CHECK_WARN_BAD_USAGE_LINES_")),
FALSE)
&& length(bad_lines <- attr(x, "bad_lines"))) {
for(doc_obj in names(bad_lines)) {
writeLines(gettextf("Bad \\usage lines found in documentation object '%s':",
doc_obj))
writeLines(paste(" ", bad_lines[[doc_obj]]))
}
writeLines("")
}
invisible(x)
}
### * checkDocStyle
checkDocStyle <-
function(package, dir, lib.loc = NULL)
{
has_namespace <- FALSE
## Argument handling.
if(!missing(package)) {
if(length(package) != 1)
stop("argument 'package' must be of length 1")
dir <- .find.package(package, lib.loc)
## Using package installed in 'dir' ...
code_dir <- file.path(dir, "R")
if(!file_test("-d", code_dir))
stop(gettextf("directory '%s' does not contain R code",
dir),
domain = NA)
docs_dir <- file.path(dir, "man")
if(!file_test("-d", docs_dir))
stop(gettextf("directory '%s' does not contain Rd sources",
dir),
domain = NA)
is_base <- basename(dir) == "base"
## Load package into code_env.
if(!is_base)
.load_package_quietly(package, lib.loc)
code_env <- .package_env(package)
objects_in_code <- objects(envir = code_env, all.names = TRUE)
## Does the package have a namespace?
if(packageHasNamespace(package, dirname(dir))) {
has_namespace <- TRUE
## Determine names of declared S3 methods and associated S3
## generics.
ns_S3_methods_db <- getNamespaceInfo(package, "S3methods")
ns_S3_generics <- ns_S3_methods_db[, 1]
ns_S3_methods <- ns_S3_methods_db[, 3]
}
}
else {
if(missing(dir))
stop("you must specify 'package' or 'dir'")
## Using sources from directory @code{dir} ...
if(!file_test("-d", dir))
stop(gettextf("directory '%s' does not exist", dir),
domain = NA)
else
dir <- file_path_as_absolute(dir)
code_dir <- file.path(dir, "R")
if(!file_test("-d", code_dir))
stop(gettextf("directory '%s' does not contain R code",
dir),
domain = NA)
docs_dir <- file.path(dir, "man")
if(!file_test("-d", docs_dir))
stop(gettextf("directory '%s' does not contain Rd sources",
dir),
domain = NA)
is_base <- basename(dir) == "base"
code_env <- new.env()
.source_assignments_in_code_dir(code_dir, code_env)
sys_data_file <- file.path(code_dir, "sysdata.rda")
if(file_test("-f", sys_data_file)) load(sys_data_file, code_env)
objects_in_code <- objects(envir = code_env, all.names = TRUE)
## Does the package have a NAMESPACE file? Note that when
## working on the sources we (currently?) cannot deal with the
## (experimental) alternative way of specifying the namespace.
if(file.exists(file.path(dir, "NAMESPACE"))) {
has_namespace <- TRUE
nsInfo <- parseNamespaceFile(basename(dir), dirname(dir))
## Determine exported objects.
OK <- objects_in_code[objects_in_code %in% nsInfo$exports]
for(p in nsInfo$exportPatterns)
OK <- c(OK, grep(p, objects_in_code, value = TRUE))
objects_in_code <- unique(OK)
## Determine names of declared S3 methods and associated S3
## generics.
ns_S3_methods_db <- .get_namespace_S3_methods_db(nsInfo)
ns_S3_generics <- ns_S3_methods_db[, 1]
ns_S3_methods <- ns_S3_methods_db[, 3]
}
}
## Find the function objects in the given package.
functions_in_code <-
objects_in_code[sapply(objects_in_code,
function(f)
is.function(get(f, envir = code_env)))
== TRUE]
## Find all generic functions in the given package and (the current)
## base package.
all_generics <- character()
env_list <- list(code_env)
if(!is_base) env_list <- c(env_list, list(baseenv()))
for(env in env_list) {
## Find all available S3 generics.
objects_in_env <- if(identical(env, code_env)) {
## We only want the exported ones anyway ...
functions_in_code
}
else
objects(envir = env, all.names = TRUE)
if(length(objects_in_env))
all_generics <-
c(all_generics,
objects_in_env[sapply(objects_in_env, .is_S3_generic, env)
== TRUE])
}
## Add internal S3 generics and S3 group generics.
all_generics <-
c(all_generics,
.get_internal_S3_generics(),
.get_S3_group_generics())
## Find all methods in the given package for the generic functions
## determined above. Store as a list indexed by the names of the
## generic functions.
methods_stop_list <- .make_S3_methods_stop_list(basename(dir))
methods_in_package <- sapply(all_generics, function(g) {
##
## We should really determine the name g dispatches for, see
## a current version of methods() [2003-07-07]. (Care is needed
## for internal generics and group generics.)
## Matching via grep() is tricky with e.g. a '$' in the name of
## the generic function ... hence substr().
name <- paste(g, ".", sep = "")
methods <-
functions_in_code[substr(functions_in_code, 1,
nchar(name, type="c")) == name]
##
methods <- methods %w/o% methods_stop_list
if(has_namespace) {
## Find registered methods for generic g.
methods <- c(methods, ns_S3_methods[ns_S3_generics == g])
}
methods
})
all_methods_in_package <- unlist(methods_in_package)
db <- if(!missing(package))
Rd_db(package, lib.loc = dirname(dir))
else
Rd_db(dir = dir)
db <- lapply(db, function(f) paste(Rd_pp(f), collapse = "\n"))
names(db) <- db_names <- .get_Rd_names_from_Rd_db(db)
db_usage_texts <-
.apply_Rd_filter_to_Rd_db(db, get_Rd_section, "usage")
db_usages <- lapply(db_usage_texts, .parse_usage_as_much_as_possible)
ind <- sapply(db_usages,
function(x) !is.null(attr(x, "bad_lines")))
bad_lines <- lapply(db_usages[ind], attr, "bad_lines")
bad_doc_objects <- list()
for(docObj in db_names) {
## Determine function names in the \usage.
exprs <- db_usages[[docObj]]
exprs <- exprs[sapply(exprs, length) > 1]
## Ordinary functions.
functions <-
as.character(sapply(exprs,
function(e) as.character(e[[1]])))
## (Note that as.character(sapply(exprs, "[[", 1)) does not do
## what we want due to backquotifying.)
## Replacement functions.
ind <- as.logical(sapply(exprs,
.is_call_from_replacement_function_usage))
if(any(ind)) {
replace_funs <-
paste(sapply(exprs[ind],
function(e) as.character(e[[2]][[1]])),
"<-",
sep = "")
functions <- c(functions, replace_funs)
}
methods_with_full_name <-
functions[functions %in% all_methods_in_package]
functions <- .transform_S3_method_markup(functions)
methods_with_generic <-
sapply(functions[functions %in% all_generics],
function(g)
functions[functions %in% methods_in_package[[g]]],
simplify = FALSE)
if((length(methods_with_generic) > 0) ||
(length(methods_with_full_name > 0)))
bad_doc_objects[[docObj]] <-
list(withGeneric = methods_with_generic,
withFullName = methods_with_full_name)
}
attr(bad_doc_objects, "bad_lines") <- bad_lines
class(bad_doc_objects) <- "checkDocStyle"
bad_doc_objects
}
print.checkDocStyle <-
function(x, ...) {
for(docObj in names(x)) {
##
## With \method{GENERIC}{CLASS} now being transformed to show
## both GENERIC and CLASS info, documenting S3 methods on the
## same page as their generic is not necessarily a problem any
## more (as one can refer to the generic or the methods in the
## documentation, in particular for the primary argument).
## Hence, even if we still provide information about this, we
## no longer print it by default. One can still access it via
## lapply(checkDocStyle("foo"), "[[", "withGeneric")
## (but of course it does not print that nicely anymore),
##
methods_with_full_name <- x[[docObj]][["withFullName"]]
if(length(methods_with_full_name > 0)) {
writeLines(gettextf("S3 methods shown with full name in documentation object '%s':",
docObj))
writeLines(strwrap(paste(methods_with_full_name,
collapse = " "),
indent = 2, exdent = 2))
writeLines("")
}
}
invisible(x)
}
### * checkFF
checkFF <-
function(package, dir, file, lib.loc = NULL,
verbose = getOption("verbose"))
{
hasNamespace <- FALSE
## Argument handling.
if(!missing(package)) {
if(length(package) != 1)
stop("argument 'package' must be of length 1")
dir <- .find.package(package, lib.loc)
## Using package installed in @code{dir} ...
code_dir <- file.path(dir, "R")
if(!file_test("-d", code_dir))
stop(gettextf("directory '%s' does not contain R code",
dir),
domain = NA)
if(basename(dir) != "base")
.load_package_quietly(package, lib.loc)
code_env <- if(packageHasNamespace(package, dirname(dir))) {
ce <- asNamespace(package)
if(exists("DLLs", envir = ce$.__NAMESPACE__.)) {
DLLs <- get("DLLs", envir = ce$.__NAMESPACE__.)
hasNamespace <- length(DLLs) > 0
}
ce
} else
.package_env(package)
}
else if(!missing(dir)) {
## Using sources from directory @code{dir} ...
if(!file_test("-d", dir))
stop(gettextf("directory '%s' does not exist", dir),
domain = NA)
else
dir <- file_path_as_absolute(dir)
if(file.exists(file.path(dir, "NAMESPACE"))) {
nm <- parseNamespaceFile(basename(dir), dirname(dir))
hasNamespace <- length(nm$dynlibs)
}
code_dir <- file.path(dir, "R")
if(!file_test("-d", code_dir))
stop(gettextf("directory '%s' does not contain R code",
dir),
domain = NA)
file <- tempfile()
on.exit(unlink(file))
if(!file.create(file)) stop("unable to create ", file)
if(!all(.file_append_ensuring_LFs(file,
list_files_with_type(code_dir,
"code"))))
stop("unable to write code files")
}
else if(missing(file)) {
stop("you must specify 'package', 'dir' or 'file'")
}
if(missing(package) && !file_test("-f", file))
stop(gettextf("file '%s' does not exist", file),
domain = NA)
##
## Should there really be a 'verbose' argument?
## It may be useful to extract all foreign function calls but then
## we would want the calls back ...
## What we currently do is the following: if 'verbose' is true, we
## show all foreign function calls in abbreviated form with the line
## ending in either 'OK' or 'MISSING', and we return the list of
## 'bad' FF calls (i.e., where the 'PACKAGE' argument is missing)
## *invisibly* (so that output is not duplicated).
## Otherwise, if not verbose, we return the list of bad FF calls.
##
bad_exprs <- list()
FF_funs <- FF_fun_names <- c(".C", ".Fortran", ".Call", ".External",
".Call.graphics", ".External.graphics")
## As pointed out by DTL, packages could use non-base FF calls for
## which missing 'PACKAGE' arguments are not necessarily a problem.
if(!missing(package)) {
is_FF_fun_from_base <-
sapply(FF_funs,
function(f) {
e <- .find_owner_env(f, code_env)
(identical(e, baseenv())
|| identical(e, .BaseNamespaceEnv))
})
FF_funs <- FF_funs[is_FF_fun_from_base]
}
## Also, need to handle base::.Call() etc ...
FF_funs <- c(FF_funs, sprintf("base::%s", FF_fun_names))
find_bad_exprs <- function(e) {
if(is.call(e) || is.expression(e)) {
##
## This picks up all calls, e.g. a$b, and they may convert
## to a vector. The function is the first element in all
## the calls we are interested in.
## BDR 2002-11-28
##
if(deparse(e[[1]])[1] %in% FF_funs) {
if(!is.character(e[[2]])) parg <- "Called with symbol"
else {
parg <- e[["PACKAGE"]]
parg <- if(!is.null(parg) && (parg != "")) "OK"
else if(!hasNamespace) {
bad_exprs <<- c(bad_exprs, e)
"MISSING"
} else "MISSING but in a function in a namespace"
}
if(verbose)
cat(deparse(e[[1]]), "(", deparse(e[[2]]),
", ...): ", parg, "\n", sep = "")
}
for(i in seq_along(e)) Recall(e[[i]])
}
}
if(!missing(package)) {
exprs <- lapply(ls(envir = code_env, all.names = TRUE),
function(f) {
f <- get(f, envir = code_env)
if(typeof(f) == "closure")
body(f)
else
NULL
})
if(.isMethodsDispatchOn()) {
## Also check the code in S4 methods.
## This may find things twice if a setMethod() with a bad FF
## call is from inside a function (e.g., InitMethods()).
for(f in methods::getGenerics(code_env)) {
meths <-
methods::linearizeMlist(methods::getMethodsMetaData(f, code_env))
bodies <- lapply(methods::slot(meths, "methods"), body)
## Exclude methods inherited from the 'appropriate'
## parent environment.
##
## Keep this in sync with similar code in undoc().
## Note that direct comparison of
## lapply(methods::slot(meths, "methods"), environment)
## to code_env is not quite right ...
penv <- .Internal(getRegisteredNamespace(as.name(package)))
if(is.environment(penv))
penv <- parent.env(penv)
else
penv <- parent.env(code_env)
if((f %in% methods::getGenerics(penv))
&& !is.null(mlist_from_penv <-
methods::getMethodsMetaData(f, penv))) {
classes_from_cenv <-
methods::slot(meths, "classes")
classes_from_penv <-
methods::slot(methods::linearizeMlist(mlist_from_penv),
"classes")
ind <- is.na(match(.make_signatures(classes_from_cenv),
.make_signatures(classes_from_penv)))
bodies <- bodies[ind]
}
##
exprs <- c(exprs, bodies)
}
}
}
else {
exprs <- try(parse(file = file, n = -1))
if(inherits(exprs, "try-error"))
stop(gettextf("parse error in file '%s'", file),
domain = NA)
}
for(i in seq_along(exprs)) find_bad_exprs(exprs[[i]])
class(bad_exprs) <- "checkFF"
if(verbose)
invisible(bad_exprs)
else
bad_exprs
}
print.checkFF <-
function(x, ...)
{
if(length(x) > 0) {
writeLines(gettextf("Foreign function calls without 'PACKAGE' argument:"))
for(i in seq_along(x)) {
writeLines(paste(deparse(x[[i]][[1]]),
"(",
deparse(x[[i]][[2]]),
", ...)",
sep = ""))
}
}
invisible(x)
}
### * checkS3methods
checkS3methods <-
function(package, dir, lib.loc = NULL)
{
has_namespace <- FALSE
## If an installed package has a namespace, we need to record the S3
## methods which are registered but not exported (so that we can
## get() them from the right place).
S3_reg <- character(0)
## Argument handling.
if(!missing(package)) {
if(length(package) != 1)
stop("argument 'package' must be of length 1")
dir <- .find.package(package, lib.loc)
## Using package installed in @code{dir} ...
code_dir <- file.path(dir, "R")
if(!file_test("-d", code_dir))
stop(gettextf("directory '%s' does not contain R code",
dir),
domain = NA)
is_base <- basename(dir) == "base"
## Load package into code_env.
if(!is_base)
.load_package_quietly(package, lib.loc)
code_env <- .package_env(package)
objects_in_code <- objects(envir = code_env, all.names = TRUE)
## Does the package have a namespace?
if(packageHasNamespace(package, dirname(dir))) {
has_namespace <- TRUE
## Determine names of declared S3 methods and associated S3
## generics.
ns_S3_methods_db <- getNamespaceInfo(package, "S3methods")
ns_S3_generics <- ns_S3_methods_db[, 1]
ns_S3_methods <- ns_S3_methods_db[, 3]
## Determine unexported but declared S3 methods.
S3_reg <- ns_S3_methods %w/o% objects_in_code
}
}
else {
if(missing(dir))
stop("you must specify 'package' or 'dir'")
## Using sources from directory @code{dir} ...
if(!file_test("-d", dir))
stop(gettextf("directory '%s' does not exist", dir),
domain = NA)
else
dir <- file_path_as_absolute(dir)
code_dir <- file.path(dir, "R")
if(!file_test("-d", code_dir))
stop(gettextf("directory '%s' does not contain R code",
dir),
domain = NA)
is_base <- basename(dir) == "base"
code_env <- new.env()
.source_assignments_in_code_dir(code_dir, code_env)
sys_data_file <- file.path(code_dir, "sysdata.rda")
if(file_test("-f", sys_data_file)) load(sys_data_file, code_env)
objects_in_code <- objects(envir = code_env, all.names = TRUE)
## Does the package have a NAMESPACE file? Note that when
## working on the sources we (currently?) cannot deal with the
## (experimental) alternative way of specifying the namespace.
if(file.exists(file.path(dir, "NAMESPACE"))) {
has_namespace <- TRUE
nsInfo <- parseNamespaceFile(basename(dir), dirname(dir))
## Determine exported objects.
OK <- objects_in_code[objects_in_code %in% nsInfo$exports]
for(p in nsInfo$exportPatterns)
OK <- c(OK, grep(p, objects_in_code, value = TRUE))
objects_in_code <- unique(OK)
## Determine names of declared S3 methods and associated S3
## generics.
ns_S3_methods_db <- .get_namespace_S3_methods_db(nsInfo)
ns_S3_generics <- ns_S3_methods_db[, 1]
ns_S3_methods <- ns_S3_methods_db[, 3]
}
}
## Find the function objects in the given package.
functions_in_code <-
objects_in_code[sapply(objects_in_code,
function(f)
is.function(get(f, envir = code_env)))
== TRUE]
methods_stop_list <- .make_S3_methods_stop_list(basename(dir))
S3_group_generics <- .get_S3_group_generics()
checkArgs <- function(g, m) {
## Do the arguments of method m (in code_env) 'extend' those of
## the generic g as seen from code_env? The method must have all
## arguments the generic has, with positional arguments of g in
## the same positions for m.
## Exception: '...' in the method swallows anything.
genfun <- get(g, envir = code_env)
gArgs <- names(formals(genfun))
if(g == "plot") gArgs <- gArgs[-2]
ogArgs <- gArgs
gm <- if(m %in% S3_reg) {
## See registerS3method() in namespace.R.
defenv <-
if (g %in% S3_group_generics) .BaseNamespaceEnv
else {
if(.isMethodsDispatchOn()
&& methods::is(genfun, "genericFunction"))
genfun <-
methods::slot(genfun, "default")@methods$ANY
if (typeof(genfun) == "closure") environment(genfun)
else .BaseNamespaceEnv
}
if(!exists(".__S3MethodsTable__.", envir = defenv,
inherits = FALSE)) {
## Happens e.g. if for some reason, we get "plot" as
## standardGeneric for "plot" defined from package
## "graphics" with its own environment which does not
## contain an S3 methods table ...
return(NULL)
}
S3Table <- get(".__S3MethodsTable__.", envir = defenv,
inherits = FALSE)
if(!exists(m, envir = S3Table)) {
warning(gettextf("declared S3 method '%s' not found",
m),
domain = NA,
call. = FALSE)
return(NULL)
} else get(m, envir = S3Table)
} else get(m, envir = code_env)
mArgs <- omArgs <- names(formals(gm))
## If m is a formula method, its first argument *may* be called
## formula. (Note that any argument name mismatch throws an
## error in current S-PLUS versions.)
if(length(grep("\\.formula$", m)) > 0) {
gArgs <- gArgs[-1]
mArgs <- mArgs[-1]
}
dotsPos <- which(gArgs == "...")
ipos <- if(length(dotsPos) > 0)
seq.int(from = 1, length = dotsPos[1] - 1)
else
seq_along(gArgs)
## careful, this could match multiply in incorrect funs.
dotsPos <- which(mArgs == "...")
if(length(dotsPos) > 0)
ipos <- ipos[seq.int(from = 1, length = dotsPos[1] - 1)]
posMatchOK <- identical(gArgs[ipos], mArgs[ipos])
argMatchOK <- all(gArgs %in% mArgs) || length(dotsPos) > 0
if(posMatchOK && argMatchOK)
NULL
else {
l <- list(ogArgs, omArgs)
names(l) <- c(g, m)
list(l)
}
}
## Deal with S3 group methods. We create a separate environment
## with pseudo-definitions for these.
S3_group_generics_env <- new.env()
assign("Math",
function(x, ...) UseMethod("Math"),
envir = S3_group_generics_env)
assign("Ops",
function(e1, e2) UseMethod("Ops"),
envir = S3_group_generics_env)
assign("Summary",
function(x, ...) UseMethod("Summary"),
envir = S3_group_generics_env)
assign("Complex",
function(x, ...) UseMethod("Complex"),
envir = S3_group_generics_env)
## Now determine the 'bad' methods in the function objects of the
## package.
bad_methods <- list()
env_list <- list(code_env, S3_group_generics_env)
if(!is_base) {
##
## Look for generics in the whole of the former base.
## Maybe eventually change this ...
## (Note that this requires that these packages are already
## attached.)
env_list <- c(env_list,
list(baseenv()),
list(as.environment("package:graphics")),
list(as.environment("package:stats")),
list(as.environment("package:utils"))
)
##
## If 'package' was given, also use the loaded namespaces and
## attached packages listed in the DESCRIPTION Depends field.
## Not sure if this is the best approach: we could also try to
## determine which namespaces/packages were made available by
## loading the package (which should work at least when run from
## R CMD check), or we could simply attach every package listed
## as a dependency ... or perhaps do both.
if(!missing(package)) {
db <- .read_description(file.path(dir, "DESCRIPTION"))
if(!is.na(depends <- db["Depends"])) {
depends <- names(.split_dependencies(depends)) %w/o% "R"
ind <- depends %in% loadedNamespaces()
if(any(ind)) {
env_list <-
c(env_list, lapply(depends[ind], getNamespace))
depends <- depends[!ind]
}
ind <- depends %in% .packages()
if(any(ind)) {
env_list <-
c(env_list, lapply(depends[ind], .package_env))
}
}
}
}
## Also want the internal S3 generics from base which are not
## .Primitive (as checkArgs() cannot deal with primitives).
all_S3_generics <- .get_internal_S3_generics()
all_S3_generics <- all_S3_generics[sapply(all_S3_generics,
.is_primitive,
baseenv())
== FALSE]
for(env in env_list) {
## Find all available S3 generics.
objects_in_env <- if(identical(env, code_env)) {
## We only want the exported ones anyway ...
functions_in_code
}
else
objects(envir = env, all.names = TRUE)
if(".no_S3_generics" %in% objects_in_env) next
S3_generics <- if(length(objects_in_env))
objects_in_env[sapply(objects_in_env, .is_S3_generic, env)
== TRUE]
else character(0)
all_S3_generics <- c(all_S3_generics, S3_generics)
}
all_S3_generics <- unique(all_S3_generics)
for(g in all_S3_generics) {
if(!exists(g, envir = code_env)) next
## Find all methods in functions_in_code for S3 generic g.
##
## We should really determine the name g dispatches for, see
## a current version of methods() [2003-07-07]. (Care is
## needed for internal generics and group generics.)
## Matching via grep() is tricky with e.g. a '$' in the name
## of the generic function ... hence substr().
name <- paste(g, ".", sep = "")
methods <-
functions_in_code[substr(functions_in_code, 1,
nchar(name, type="c")) == name]
##
methods <- methods %w/o% methods_stop_list
if(has_namespace) {
## Find registered methods for generic g.
methods <- c(methods, ns_S3_methods[ns_S3_generics == g])
}
for(m in methods)
## both all() and all.equal() are generic.
bad_methods <- if(g == "all") {
m1 <- m[-grep("^all\\.equal", m)]
c(bad_methods, if(length(m1)) checkArgs(g, m1))
} else c(bad_methods, checkArgs(g, m))
}
class(bad_methods) <- "checkS3methods"
bad_methods
}
print.checkS3methods <-
function(x, ...)
{
format_args <- function(s)
paste("function(", paste(s, collapse = ", "), ")", sep = "")
for(entry in x) {
writeLines(c(paste(names(entry)[1], ":", sep = ""),
strwrap(format_args(entry[[1]]),
indent = 2, exdent = 11),
paste(names(entry)[2], ":", sep = ""),
strwrap(format_args(entry[[2]]),
indent = 2, exdent = 11),
""))
}
invisible(x)
}
### * checkReplaceFuns
checkReplaceFuns <-
function(package, dir, lib.loc = NULL)
{
has_namespace <- FALSE
## Argument handling.
if(!missing(package)) {
if(length(package) != 1)
stop("argument 'package' must be of length 1")
dir <- .find.package(package, lib.loc)
## Using package installed in @code{dir} ...
code_dir <- file.path(dir, "R")
if(!file_test("-d", code_dir))
stop(gettextf("directory '%s' does not contain R code",
dir),
domain = NA)
is_base <- basename(dir) == "base"
## Load package into code_env.
if(!is_base)
.load_package_quietly(package, lib.loc)
## In case the package has a namespace, we really want to check
## all replacement functions in the package. (If not, we need
## to change the code for the non-installed case to only look at
## exported (replacement) functions.)
if(packageHasNamespace(package, dirname(dir))) {
has_namespace <- TRUE
code_env <- asNamespace(package)
ns_S3_methods_db <- getNamespaceInfo(package, "S3methods")
}
else
code_env <- .package_env(package)
}
else {
if(missing(dir))
stop("you must specify 'package' or 'dir'")
## Using sources from directory @code{dir} ...
if(!file_test("-d", dir))
stop(gettextf("directory '%s' does not exist", dir),
domain = NA)
else
dir <- file_path_as_absolute(dir)
code_dir <- file.path(dir, "R")
if(!file_test("-d", code_dir))
stop(gettextf("directory '%s' does not contain R code",
dir),
domain = NA)
is_base <- basename(dir) == "base"
code_env <- new.env()
.source_assignments_in_code_dir(code_dir, code_env)
sys_data_file <- file.path(code_dir, "sysdata.rda")
if(file_test("-f", sys_data_file)) load(sys_data_file, code_env)
## Does the package have a NAMESPACE file? Note that when
## working on the sources we (currently?) cannot deal with the
## (experimental) alternative way of specifying the namespace.
if(file.exists(file.path(dir, "NAMESPACE"))) {
has_namespace <- TRUE
nsInfo <- parseNamespaceFile(basename(dir), dirname(dir))
ns_S3_methods_db <- .get_namespace_S3_methods_db(nsInfo)
}
}
objects_in_code <- objects(envir = code_env, all.names = TRUE)
replace_funs <- character()
if(has_namespace) {
ns_S3_generics <- ns_S3_methods_db[, 1]
ns_S3_methods <- ns_S3_methods_db[, 3]
## S3 replacement methods from namespace registration?
idx <- grep("<-$", ns_S3_generics)
if(any(idx)) replace_funs <- ns_S3_methods[idx]
## Now remove the functions registered as S3 methods.
objects_in_code <- objects_in_code %w/o% ns_S3_methods
}
replace_funs <-
c(replace_funs, grep("<-", objects_in_code, value = TRUE))
.check_last_formal_arg <- function(f) {
arg_names <- names(formals(f))
if(!length(arg_names))
TRUE # most likely a .Primitive()
else
identical(arg_names[length(arg_names)], "value")
}
## Find the replacement functions (which have formal arguments) with
## last arg not named 'value'.
bad_replace_funs <- if(length(replace_funs)) {
replace_funs[sapply(replace_funs, function(f) {
## Always get the functions from code_env ...
## Should maybe get S3 methods from the registry ...
f <- get(f, envir = code_env)
if(!is.function(f)) return(TRUE)
.check_last_formal_arg(f)
}) == FALSE]} else character(0)
if(.isMethodsDispatchOn()) {
S4_generics <- methods::getGenerics(code_env)
## Assume that the ones with names ending in '<-' are always
## replacement functions.
S4_generics <- grep("<-$", S4_generics, value = TRUE)
bad_S4_replace_methods <-
sapply(S4_generics,
function(f) {
meths <- methods::linearizeMlist(methods::getMethodsMetaData(f, code_env))
ind <- which(sapply(methods::slot(meths,
"methods"),
.check_last_formal_arg)
== FALSE)
if(!length(ind))
character()
else {
sigs <-
sapply(methods::slot(meths,
"classes")[ind],
paste, collapse = ",")
paste("\\S4method{", f, "}{", sigs, "}",
sep = "")
}
})
bad_replace_funs <-
c(bad_replace_funs,
unlist(bad_S4_replace_methods, use.names = FALSE))
}
class(bad_replace_funs) <- "checkReplaceFuns"
bad_replace_funs
}
print.checkReplaceFuns <-
function(x, ...)
{
if(length(x) > 0) .pretty_print(unclass(x))
invisible(x)
}
### * checkTnF
checkTnF <-
function(package, dir, file, lib.loc = NULL)
{
code_files <- docs_files <- character(0)
## Argument handling.
if(!missing(package)) {
if(length(package) != 1)
stop("argument 'package' must be of length 1")
## Using package installed in @code{dir} ...
dir <- .find.package(package, lib.loc)
if(file.exists(file.path(dir, "R", "all.rda"))) {
warning("cannot check R code installed as image")
}
code_file <- file.path(dir, "R", package)
if(file.exists(code_file)) # could be data-only
code_files <- code_file
example_dir <- file.path(dir, "R-ex")
if(file_test("-d", example_dir)) {
code_files <- c(code_files,
list_files_with_exts(example_dir, "R"))
}
}
else if(!missing(dir)) {
## Using sources from directory @code{dir} ...
if(!file_test("-d", dir))
stop(gettextf("directory '%s' does not exist", dir),
domain = NA)
else
dir <- file_path_as_absolute(dir)
code_dir <- file.path(dir, "R")
if(file_test("-d", code_dir)) # could be data-only
code_files <- list_files_with_type(code_dir, "code")
docs_dir <- file.path(dir, "man")
if(file_test("-d", docs_dir))
docs_files <- list_files_with_type(docs_dir, "docs")
}
else if(!missing(file)) {
if(!file_test("-f", file))
stop(gettextf("file '%s' does not exist", file),
domain = NA)
else
code_files <- file
}
else
stop("you must specify 'package', 'dir' or 'file'")
find_TnF_in_code <- function(file, txt) {
## If 'txt' is given, it contains the extracted examples from
## the R documentation file 'file'. Otherwise, 'file' gives a
## file with (just) R code.
matches <- list()
TnF <- c("T", "F")
find_bad_exprs <- function(e, p) {
if(is.name(e)
&& (as.character(e) %in% TnF)
&& !is.null(p)) {
## Need the 'list()' to deal with T/F in function
## arglists which are pairlists ...
matches <<- c(matches, list(p))
}
else if(is.recursive(e)) {
for(i in seq_along(e)) Recall(e[[i]], e)
}
}
if(missing(txt)) {
exprs <- try(parse(file = file, n = -1))
if(inherits(exprs, "try-error"))
stop(gettextf("parse error in file '%s'", file),
domain = NA)
}
else {
exprs <- try(parse(text = txt))
if(inherits(exprs, "try-error"))
stop(gettextf("parse error in examples from file '%s'",
file),
domain = NA)
}
for(i in seq_along(exprs))
find_bad_exprs(exprs[[i]], NULL)
matches
}
bad_exprs <- list()
for(file in code_files) {
exprs <- find_TnF_in_code(file)
if(length(exprs) > 0) {
exprs <- list(exprs)
names(exprs) <- file
bad_exprs <- c(bad_exprs, exprs)
}
}
for(file in docs_files) {
txt <- paste(Rd_pp(.read_Rd_lines_quietly(file)),
collapse = "\n")
txt <- .get_Rd_example_code(txt)
exprs <- find_TnF_in_code(file, txt)
if(length(exprs) > 0) {
exprs <- list(exprs)
names(exprs) <- file
bad_exprs <- c(bad_exprs, exprs)
}
}
class(bad_exprs) <- "checkTnF"
bad_exprs
}
print.checkTnF <-
function(x, ...)
{
for(fname in names(x)) {
writeLines(gettextf("File '%s':", fname))
xfname <- x[[fname]]
for(i in seq_along(xfname)) {
writeLines(strwrap(gettextf("found T/F in %s",
paste(deparse(xfname[[i]]),
collapse = "")),
exdent = 4))
}
writeLines("")
}
invisible(x)
}
### * .check_package_depends
## changed in 2.3.0 to refer to a source dir.
.check_package_depends <-
function(dir)
{
if(length(dir) != 1)
stop("argument 'package' must be of length 1")
## We definitely need a valid DESCRIPTION file.
db <- .read_description(file.path(dir, "DESCRIPTION"))
package_name <- basename(dir)
## (Should really use db["Package"], but then we need to check
## whether this is really there ...)
depends <- .get_requires_from_package_db(db, "Depends")
imports <- .get_requires_from_package_db(db, "Imports")
suggests <- .get_requires_from_package_db(db, "Suggests")
## Need this to handle bundles ...
contains <- .get_contains_from_package_db(db)
standard_package_names <- .get_standard_package_names()
bad_depends <- list()
## Are all packages listed in Depends/Suggests/Imports installed?
## Need to treat specially the former stub packages.
reqs <- unique(c(depends,
imports,
if(!identical(as.logical(Sys.getenv("_R_CHECK_FORCE_SUGGESTS_")),
FALSE))
suggests))
reqs <- reqs %w/o% utils::installed.packages()[ , "Package"]
m <- reqs %in% standard_package_names$stubs
if(length(reqs[!m]))
bad_depends$required_but_not_installed <- reqs[!m]
if(length(reqs[m]))
bad_depends$required_but_stub <- reqs[m]
## Are all vignette dependencies at least suggested or equal to
## the package name?
vignette_dir <- file.path(dir, "inst", "doc")
if(file_test("-d", vignette_dir)
&& length(list_files_with_type(vignette_dir, "vignette"))) {
reqs <- unique(unlist(.build_vignette_index(vignette_dir)$Depends))
## For the time being, ignore base packages missing from the
## DESCRIPTION dependencies even if explicitly given as vignette
## dependencies.
reqs <- reqs %w/o% c(depends, imports, suggests, package_name,
standard_package_names$base)
if(length(reqs))
bad_depends$missing_vignette_depends <- reqs
}
## Are all namespace dependencies listed as package dependencies?
if(file_test("-f", file.path(dir, "NAMESPACE"))) {
reqs <- .get_namespace_package_depends(dir)
##
## Not clear whether we want to require *all* namespace package
## dependencies listed in DESCRIPTION, or e.g. just the ones on
## non-base packages. Do the latter for time being ...
## Actually we need to know at least about S4-using packages,
## since we need to reinstall if those change.
allowed_imports <-
standard_package_names$base %w/o% c("methods", "stats4")
reqs <- reqs %w/o% c(contains, imports, depends, allowed_imports)
## Note that for bundles we currently cannot have package
## dependencies different from bundle ones, and clearly a bundle
## cannot depend on something it contains ...
##
if(length(reqs))
bad_depends$missing_namespace_depends <- reqs
}
class(bad_depends) <- "check_package_depends"
bad_depends
}
print.check_package_depends <-
function(x, ...)
{
if(length(bad <- x$required_but_not_installed)) {
writeLines(gettext("Packages required but not available:"))
.pretty_print(bad)
writeLines("")
}
if(length(bad <- x$required_but_stub)) {
writeLines(gettext("Former standard packages required but now defunct:"))
.pretty_print(bad)
writeLines("")
}
if(length(bad <- x$missing_vignette_depends)) {
writeLines(gettext("Vignette dependencies not required:"))
.pretty_print(bad)
msg <- gettext("Vignette dependencies (\\VignetteDepends{} entries) must be contained in the DESCRIPTION Depends/Suggests entries.")
writeLines(strwrap(msg))
writeLines("")
}
if(length(bad <- x$missing_namespace_depends)) {
writeLines(gettext("Namespace dependencies not required:"))
.pretty_print(bad)
writeLines("")
}
invisible(x)
}
### * check_Rd_files_in_package
##
## We currently have two (internal) check_Rd_files* functions.
##
## The primary one is check_Rd_files_in_man_dir, as this always works,
## but note that its 'dir' argument really is a directory containing Rd
## source files, and not a package top-level source subdirectory (as
## indicated by 'man_dir' in the function name).
##
## Function check_Rd_files_in_package only works for packages installed
## with R 2.0 or better (as it requires that the installed Rd sources
## have the Rd file names preserved).
##
## So perhaps eventually unify these functions for 2.1? Currently, it
## seems a bad idea to have check_Rd_files(dir, package, lib.loc) which
## has a different interface than the other QC functions ...
##
## Of course, all of this is conditional on not moving away from Rd
## format ...
##
check_Rd_files_in_package <-
function(package, lib.loc = NULL)
{
if(length(package) != 1)
stop("argument 'package' must be of length 1")
## (Actually, Rd_db() would check on this too ...)
db <- Rd_db(package, lib.loc)
if(is.null(names(db)))
stop("Package Rd sources were installed without preserving Rd file names.\n",
"Please reinstall using a current version of R.")
.check_Rd_files_in_Rd_db(db)
}
### * check_Rd_files_in_man_dir
check_Rd_files_in_man_dir <-
function(dir)
{
if(!file_test("-d", dir))
stop(gettextf("directory '%s' does not exist", dir),
domain = NA)
dir <- file_path_as_absolute(dir)
## Argh. We cannot call Rd_db() directly, because this works on
## the top-level package source directory ...
Rd_files <- list_files_with_type(file.path(dir), "docs")
db <- lapply(Rd_files, .read_Rd_lines_quietly)
names(db) <- Rd_files
.check_Rd_files_in_Rd_db(db)
}
### * .check_Rd_files_in_Rd_db
.check_Rd_files_in_Rd_db <-
function(db)
{
standard_keywords <- .get_standard_Rd_keywords()
mandatory_tags <- c("name", "title", "description")
## We also need
## alias keyword
## but we handle these differently ...
unique_tags <-
c("name", "title", "description", "usage", "arguments",
"format", "details", "value", "references", "source",
"seealso", "examples", "note", "author", "synopsis",
"docType", "encoding")
known_tags <- c(unique_tags,
"section",
## Allow for empty keywords (these do not make it
## into the metadata).
"keyword",
## Keep this for back-compatibility ...
"non_function")
## Note that we treat \alias and \keyword entries as metadata.
files_with_surely_bad_Rd <- list()
files_with_likely_bad_Rd <- list()
files_with_unknown_encoding <- NULL
files_with_non_ASCII_metadata <- NULL
files_with_non_ASCII_section_titles <- NULL
files_with_missing_mandatory_tags <- NULL
files_with_duplicated_unique_tags <- NULL
files_with_unknown_tags <- NULL
files_with_bad_name <- files_with_bad_title <- NULL
files_with_bad_keywords <- NULL
db_aliases <- vector("list", length(db))
names(db_aliases) <- names(db)
for(f in names(db)) {
x <- tryCatch(Rd_parse(text = db[[f]]), error = .identity)
if(inherits(x, "error")) {
files_with_surely_bad_Rd[[f]] <- conditionMessage(x)
next
}
db_aliases[[f]] <- unique(x$meta$aliases)
if(length(x$rest))
files_with_likely_bad_Rd[[f]] <- x$rest
if(length(x$meta$encoding) && is.na(x$meta$encoding))
files_with_unknown_encoding <-
c(files_with_unknown_encoding, f)
for(tag in c("aliases", "doc_type", "encoding")) {
if(any(ind <- !.is_ASCII(x$meta[[tag]])))
files_with_non_ASCII_metadata <-
rbind(files_with_non_ASCII_metadata,
cbind(f, tag, x$meta[[tag]][ind]))
}
## Non-ASCII user-defined section titles.
##
## Rd_parse() re-encodes these if necessary (and possible), but
## we should still be able to catch the non-ASCII ones provided
## that the native encoding extends ASCII.
user_defined_section_titles <- sapply(x$data$tags, "[", 2)
if(any(ind <- !.is_ASCII(user_defined_section_titles)))
files_with_non_ASCII_section_titles <-
rbind(files_with_non_ASCII_section_titles,
cbind(f, user_defined_section_titles[ind]))
##
tags <- sapply(x$data$tags, "[[", 1)
## Let's not worry about named sections for the time being ...
bad_tags <- c(mandatory_tags %w/o% tags,
if(!length(x$meta$aliases)) "alias",
## Allow for empty keywords (these do not make it
## into the metadata).
if(!(length(x$meta$keywords)
|| any(grep("^[[:space:]]*$",
x$data$vals[tags == "keyword"]))))
"keyword")
if(length(bad_tags))
files_with_missing_mandatory_tags <-
rbind(files_with_missing_mandatory_tags,
cbind(f, bad_tags))
ind <- which(tags == "name")[1]
if(is.na(ind))
files_with_bad_name <- c(files_with_bad_name, f)
ind <- which(tags == "title")[1]
if(is.na(ind) ||
(regexpr("^[[:space:]]*$", x$data$vals[[ind]]) != -1))
files_with_bad_title <- c(files_with_bad_title, f)
bad_tags <- intersect(tags[duplicated(tags)], unique_tags)
if(length(bad_tags))
files_with_duplicated_unique_tags <-
rbind(files_with_duplicated_unique_tags,
cbind(f, bad_tags))
bad_tags <- unique(tags) %w/o% known_tags
if(length(bad_tags))
files_with_unknown_tags <-
rbind(files_with_unknown_tags,
cbind(f, bad_tags))
bad_keywords <- x$meta$keywords %w/o% standard_keywords
if(length(bad_keywords))
files_with_bad_keywords <-
rbind(files_with_bad_keywords,
cbind(f, bad_keywords))
}
db_aliases_by_db_names <-
split(rep(names(db_aliases), sapply(db_aliases, length)),
unlist(db_aliases, use.names = FALSE))
files_with_duplicated_aliases <-
db_aliases_by_db_names[sapply(db_aliases_by_db_names,
length) > 1]
val <- list(files_with_surely_bad_Rd,
files_with_likely_bad_Rd,
files_with_unknown_encoding,
files_with_non_ASCII_metadata,
files_with_non_ASCII_section_titles,
files_with_missing_mandatory_tags,
files_with_duplicated_unique_tags,
files_with_unknown_tags,
files_with_bad_name,
files_with_bad_title,
files_with_bad_keywords,
files_with_duplicated_aliases)
names(val) <-
c("files_with_surely_bad_Rd",
"files_with_likely_bad_Rd",
"files_with_unknown_encoding",
"files_with_non_ASCII_metadata",
"files_with_non_ASCII_section_titles",
"files_with_missing_mandatory_tags",
"files_with_duplicated_unique_tags",
"files_with_unknown_tags",
"files_with_bad_name",
"files_with_bad_title",
"files_with_bad_keywords",
"files_with_duplicated_aliases")
class(val) <- "check_Rd_files_in_Rd_db"
val
}
print.check_Rd_files_in_Rd_db <-
function(x, ...)
{
if(length(x$files_with_surely_bad_Rd)) {
writeLines(gettext("Rd files with syntax errors:"))
bad <- x$files_with_surely_bad_Rd
for(i in seq_along(bad)) {
writeLines(c(paste(" ", names(bad)[i], ":", sep = ""),
strwrap(bad[[i]], indent = 4, exdent = 4)))
}
writeLines("")
}
if(length(x$files_with_likely_bad_Rd)) {
bad <- x$files_with_likely_bad_Rd
## Do not warn about stray top-level text which is just
## whitespace and closing braces (i.e., "too many" closing
## braces at top level). These are not quite correct Rd, but
## can safely be ignored, as Rdconv does.
bad <- lapply(bad,
function(x) x[regexpr("^[[:space:]}]*$", x) == -1])
bad <- bad[sapply(bad, length) > 0]
if(length(bad)) {
writeLines(gettext("Rd files with likely Rd problems:"))
for(i in seq_along(bad)) {
writeLines(gettextf("Unaccounted top-level text in file '%s':",
names(bad)[i]))
tags <- names(bad[[i]])
if(any(ind <- tags != ""))
tags[ind] <- gettextf("Following section '%s'",
tags[ind])
tags[!ind] <- "Preceding all sections"
vals <- as.character(bad[[i]])
long <- nchar(vals, type="c") >= 128 # Why 128? Why not?
vals <- paste(sapply(substr(vals, 1, 127), deparse, 128),
ifelse(long, " [truncated]", ""), sep = "")
writeLines(c(paste(tags, vals, sep = c(":\n", "\n")), ""))
}
}
}
if(length(x$files_with_unknown_encoding)) {
writeLines(c(gettext("Rd files with unknown encoding:"),
paste(" ", x$files_with_unknown_encoding),
""))
}
if(length(x$files_with_non_ASCII_metadata)) {
writeLines(gettext("Rd files with invalid non-ASCII metadata:"))
bad <- x$files_with_non_ASCII_metadata
## Reinstate the Rd markup tags for better intelligibility.
bad[ , 2] <- sub("aliases", "\\\\alias", bad[ , 2])
bad[ , 2] <- sub("doc_type", "\\\\docType", bad[ , 2])
bad[ , 2] <- sub("encoding", "\\\\encoding", bad[ , 2])
ind <- split(seq_len(NROW(bad)), bad[, 1])
for(i in seq_along(ind)) {
writeLines(c(paste(" ", paste(names(ind)[i], ":", sep = "")),
paste(" ",
apply(bad[ind[[i]], -1, drop = FALSE],
1, paste, collapse = " "))))
}
writeLines("")
}
if(length(x$files_with_non_ASCII_section_titles)) {
writeLines(gettext("Rd files with non-ASCII section titles:"))
bad <- x$files_with_non_ASCII_section_titles
bad <- split(bad[, 2], bad[, 1])
for(i in seq_along(bad)) {
writeLines(c(paste(" ", paste(names(bad)[i], ":", sep = "")),
strwrap(bad[[i]], indent = 4, exdent = 6),
""))
}
}
if(length(x$files_with_bad_name)) {
writeLines(c(gettextf("Rd files with missing or empty '\\name':"),
paste(" ", x$files_with_bad_name),
""))
}
if(length(x$files_with_bad_title)) {
writeLines(c(gettextf("Rd files with missing or empty '\\title':"),
paste(" ", x$files_with_bad_title),
""))
}
if(length(x$files_with_missing_mandatory_tags)) {
bad <- x$files_with_missing_mandatory_tags
bad <- split(bad[, 1], bad[, 2])
for(i in seq_along(bad)) {
writeLines(c(gettextf("Rd files without '%s':",
names(bad)[i]),
paste(" ", bad[[i]])))
}
writeLines(gettext("These entries are required in an Rd file.\n"))
}
if(length(x$files_with_duplicated_unique_tags)) {
bad <- x$files_with_duplicated_unique_tags
bad <- split(bad[, 1], bad[, 2])
for(i in seq_along(bad)) {
writeLines(c(gettextf("Rd files with duplicate '%s':",
names(bad)[i]),
paste(" ", bad[[i]])))
}
writeLines(gettext("These entries must be unique in an Rd file.\n"))
}
if(length(x$files_with_unknown_tags)) {
writeLines(gettextf("Rd files with unknown sections:"))
bad <- x$files_with_unknown_tags
bad <- split(bad[, 2], bad[, 1])
for(i in seq_along(bad)) {
writeLines(strwrap(paste(names(bad)[i], ": ",
paste(bad[[i]], collapse = " "),
"\n", sep = ""),
indent = 2, exdent = 4))
}
writeLines("")
}
if(length(x$files_with_bad_keywords)) {
writeLines(gettext("Rd files with non-standard keywords:"))
bad <- x$files_with_bad_keywords
bad <- split(bad[, 2], bad[, 1])
for(i in seq_along(bad)) {
writeLines(strwrap(paste(names(bad)[i], ": ",
paste(bad[[i]], collapse = " "),
"\n", sep = ""),
indent = 2, exdent = 4))
}
msg <-
gettext("Each '\\keyword' entry should specify one of the standard keywords (as listed in file 'KEYWORDS' in the R documentation directory).")
writeLines(c(strwrap(msg), ""))
}
if(length(x$files_with_duplicated_aliases)) {
bad <- x$files_with_duplicated_aliases
for(alias in names(bad)) {
writeLines(gettextf("Rd files with duplicated alias '%s':", alias))
.pretty_print(bad[[alias]])
}
writeLines("")
}
invisible(x)
}
### * .check_package_description
.check_package_description <-
function(dfile)
{
dfile <- file_path_as_absolute(dfile)
db <- .read_description(dfile)
standard_package_names <- .get_standard_package_names()
valid_package_name_regexp <-
.standard_regexps()$valid_package_name
valid_package_version_regexp <-
.standard_regexps()$valid_package_version
is_base_package <-
!is.na(priority <- db["Priority"]) && priority == "base"
out <- list() # For the time being ...
## Check encoding-related things first.
## All field tags must be ASCII.
if(any(ind <- !.is_ASCII(names(db))))
out$fields_with_non_ASCII_tags <- names(db)[ind]
## For all fields used by the R package management system, values
## must be ASCII we well (so that the RPM works in a C locale).
ASCII_fields <- c("Package", "Version", "Depends", "Suggests",
"Imports", "Priority", "Encoding")
ASCII_fields <- ASCII_fields[ASCII_fields %in% names(db)]
if(any(ind <- !.is_ASCII(db[ASCII_fields])))
out$fields_with_non_ASCII_values <- ASCII_fields[ind]
## Determine encoding and re-encode if necessary and possible.
if("Encoding" %in% names(db)) {
encoding <- db["Encoding"]
if((Sys.getlocale("LC_CTYPE") != "C")
&& capabilities("iconv"))
db <- iconv(db, encoding, "")
}
else if(!all(.is_ISO_8859(db))) {
## No valid Encoding metadata.
## Determine whether we can assume Latin1.
out$missing_encoding <- TRUE
}
if(any(is.na(nchar(db, "c")))) {
## Ouch, invalid in the current locale.
## (Can only happen in a MBCS locale.)
## Try re-encoding from Latin1.
if(capabilities("iconv"))
db <- iconv(db, "latin1", "")
else
stop("Found invalid multi-byte character data.", "\n",
"Cannot re-encode because iconv is not available.", "\n",
"Try running R in a single-byte locale.")
}
## Mandatory entries in DESCRIPTION:
## Package, Version, License, Description, Title, Author,
## Maintainer.
required_fields <- c("Package", "Version", "License", "Description",
"Title", "Author", "Maintainer")
if(any(i <- which(is.na(match(required_fields, names(db))))))
out$missing_required_fields <- required_fields[i]
val <- package_name <- db["Package"]
if(!is.na(val)) {
tmp <- character()
if(regexpr(sprintf("^%s$", valid_package_name_regexp), val) == -1
&& regexpr("^Translation-[[:alnum:].]+$", val) == -1)
tmp <- c(tmp, gettext("Malformed package name"))
##
## Not clear if we really want to do this. The Perl code still
## seemed to assume that when checking a package, package name
## and 'directory' (i.e., the base name of the directory with
## the DESCRIPTION metadata) need to be the same.
## if(val != basename(dirname(dfile)))
## tmp <- c(tmp, "Package name differs from dir name.")
##
if(!is_base_package) {
if(val %in% standard_package_names$base)
tmp <- c(tmp,
c(gettext("Invalid package name."),
gettext("This is the name of a base package.")))
else if(val %in% standard_package_names$stubs)
tmp <- c(tmp,
c(gettext("Invalid package name."),
gettext("This name was used for a base package and is remapped by library().")))
}
if(length(tmp))
out$bad_package <- tmp
}
if(!is.na(val <- db["Version"])
&& !is_base_package
&& (regexpr(sprintf("^%s$", valid_package_version_regexp),
val) == -1))
out$bad_version <- val
if(!is.na(val <- db["Maintainer"])
&& (regexpr("(^[^<>]*<[^<>@]+@[^<>@]+> *$|ORPHANED)", val)
== -1))
out$bad_maintainer <- val
## Optional entries in DESCRIPTION:
## Depends/Suggests/Imports, Namespace, Priority.
## These must be correct if present.
val <- db[match(c("Depends", "Suggests", "Imports", "Enhances"),
names(db), nomatch = 0)]
if(length(val)) {
depends <- .strip_whitespace(unlist(strsplit(val, ",")))
bad_dep_entry <- bad_dep_op <- bad_dep_version <- character()
dep_regexp <-
paste("^[[:space:]]*",
paste("(", valid_package_name_regexp, ")", sep = ""),
"([[:space:]]*\\(([^) ]+)[[:space:]]+([^) ]+)\\))?",
"[[:space:]]*$",
sep = "")
for(dep in depends) {
if(regexpr(dep_regexp, dep) == -1) {
## Entry does not match the regexp.
bad_dep_entry <- c(bad_dep_entry, dep)
next
}
if(nchar(sub(dep_regexp, "\\2", dep))) {
## If not just a valid package name ...
if(!sub(dep_regexp, "\\3", dep) %in% c("<=", ">="))
bad_dep_op <- c(bad_dep_op, dep)
else if(regexpr(sprintf("^%s$",
valid_package_version_regexp),
sub(dep_regexp, "\\4", dep)) == -1)
bad_dep_version <- c(bad_dep_version, dep)
}
}
if(length(c(bad_dep_entry, bad_dep_op, bad_dep_version)))
out$bad_depends_or_suggests_or_imports <-
list(bad_dep_entry = bad_dep_entry,
bad_dep_op = bad_dep_op,
bad_dep_version = bad_dep_version)
}
if(!is.na(val <- db["Namespace"])
&& !is.na(package_name)
&& (val != package_name))
out$bad_namespace <- val
if(!is.na(val <- db["Priority"])
&& !is.na(package_name)
&& (tolower(val) %in% c("base", "recommended", "defunct-base"))
&& !(package_name %in% unlist(standard_package_names)))
out$bad_priority <- val
class(out) <- "check_package_description"
out
}
print.check_package_description <-
function(x, ...)
{
if(length(x$missing_encoding))
writeLines(c(gettext("Unknown encoding"), ""))
if(length(x$fields_with_non_ASCII_tags)) {
writeLines(gettext("Fields with non-ASCII tags:"))
.pretty_print(x$fields_with_non_ASCII_tags)
writeLines(c(gettext("All field tags must be ASCII."), ""))
}
if(length(x$fields_with_non_ASCII_values)) {
writeLines(gettext("Fields with non-ASCII values:"))
.pretty_print(x$fields_with_non_ASCII_values)
writeLines(c(gettext("These fields must have ASCII values."), ""))
}
if(length(x$missing_required_fields)) {
writeLines(gettext("Required fields missing:"))
.pretty_print(x$missing_required_fields)
writeLines("")
}
if(length(x$bad_package))
writeLines(c(strwrap(x$bad_package), ""))
if(length(x$bad_version))
writeLines(c(gettext("Malformed package version."), ""))
if(length(x$bad_maintainer))
writeLines(c(gettext("Malformed maintainer field."), ""))
if(any(as.integer(sapply(x$bad_depends_or_suggests_or_imports, length)))) {
bad <- x$bad_depends_or_suggests_or_imports
writeLines(gettext("Malformed Depends or Suggests or Imports or Enhances field."))
if(length(bad$bad_dep_entry)) {
tmp <- c(gettext("Offending entries:"),
paste(" ", bad$bad_dep_entry),
strwrap(gettextf("Entries must be names of packages optionally followed by '<=' or '>=', white space, and a valid version number in parentheses.")))
writeLines(tmp)
}
if(length(bad$bad_dep_op)) {
tmp <- c(gettext("Entries with infeasible comparison operator:"),
paste(" ", bad$bad_dep_entry),
strwrap(gettextf("Only operators '<=' and '>=' are possible.")))
writeLines(tmp)
}
if(length(bad$bad_dep_version)) {
tmp <- c(gettext("Entries with infeasible version number:"),
paste(" ", bad$bad_dep_version),
strwrap(gettextf("Version numbers must be sequences of at least two non-negative integers, separated by single '.' or '-'.")))
writeLines(tmp)
}
writeLines("")
}
if(length(x$bad_namespace))
writeLines(c(gettext("Package name and namespace differ."), ""))
if(length(x$bad_priority))
writeLines(c(gettext("Invalid Priority field."),
strwrap(gettextf("Packages with priorities 'base' or 'recommended' or 'defunct-base' must already be known to R.")),
""))
if(any(as.integer(sapply(x, length))))
writeLines(c(strwrap(gettextf("See the information on DESCRIPTION files in section 'Creating R packages' of the 'Writing R Extensions' manual.")),
""))
invisible(x)
}
### * .check_make_vars
.check_make_vars <-
function(dir)
{
bad_flags <- list()
class(bad_flags) <- "check_make_vars"
paths <- file.path(dir, c("Makevars.in", "Makevars"))
paths <- paths[file_test("-f", paths)]
if(!length(paths)) return(bad_flags)
mfile <- paths[1]
lines <-
tryCatch(system(sprintf("%s -f %s -f %s",
Sys.getenv("MAKE"),
shQuote(mfile),
shQuote(file.path(R.home("share"),
"make", "check.mk"))),
intern = TRUE,
if(identical(.Platform$OS.type, "unix"))
ignore.stderr = TRUE),
error = .identity)
if(!length(lines) || inherits(lines, "error"))
return(bad_flags)
## Try to be careful ...
lines <- lines[regexpr("^PKG_[A-Z]*FLAGS: ", lines) > -1]
names <- sub(":.*", "", lines)
lines <- sub("^PKG_[A-Z]*FLAGS: ", "", lines)
flags <- strsplit(lines, "[[:space:]]+")
## Bad flags:
## -O*
## (BDR: for example Sun Fortran compilers used to accept -O
## but not -O2, and VC++ accepts -Ox (literal x) but not -O.)
## -Wall -pedantic -ansi -traditional -std* -f* -m* [GCC]
## -x [Solaris]
## -q [AIX]
## It is hard to think of anything apart from -I* and -D* that is
## safe for general use ...
bad_flags_regexp <-
sprintf("^-(%s)$",
paste(c("O.*",
"Wall", "ansi", "pedantic", "traditiona",
"f.*", "m.*", "std.*",
"x",
"q"),
collapse = "|"))
for(i in seq_along(lines)) {
bad <- grep(bad_flags_regexp, flags[[i]], value = TRUE)
if(length(bad))
bad_flags <- c(bad_flags,
structure(list(bad), names = names[i]))
}
class(bad_flags) <- "check_make_vars"
bad_flags
}
print.check_make_vars <-
function(x, ...)
{
if(length(x) > 0) {
for(i in seq_along(x)) {
writeLines(c(gettextf("Non-portable flags in variable '%s':",
names(x)[i]),
sprintf(" %s", paste(x[[i]], collapse = " "))))
}
}
invisible(x)
}
### * .check_code_usage_in_package
.check_code_usage_in_package <-
function(package, lib.loc = NULL)
{
is_base <- package == "base"
if(!is_base)
.load_package_quietly(package, lib.loc)
## A simple function for catching the output from the codetools
## analysis using the checkUsage report mechanism.
out <- character()
foo <- function(x) out <<- c(out, x)
## (Simpler than using a variant of capture.output().)
## Of course, it would be nice to return a suitably structured
## result, but we can always do this by suitably splitting the
## messages on the double colons ...
##
## Eventually, we should be able to specify a codetools "profile"
## for checking.
##
suppressMessages(codetools::checkUsagePackage(package,
report = foo,
suppressLocalUnused = TRUE,
skipWith = TRUE))
class(out) <- "check_code_usage_in_package"
out
}
print.check_code_usage_in_package <-
function(x, ...)
{
if(length(x) > 0)
writeLines(strwrap(x, indent = 0, exdent = 2))
invisible(x)
}
### * .check_Rd_xrefs
.check_Rd_xrefs <-
function(package, dir, lib.loc = NULL)
{
## Build a db with all possible link targets (aliases) in the base
## and recommended packages.
aliases <-
lapply(unlist(.get_standard_package_names()[c("base",
"recommended")],
use.names = FALSE),
Rd_aliases, lib.loc = .Library)
## Add the aliases from the package itself, and build a db with all
## \link xrefs in the package Rd objects.
if(!missing(package)) {
aliases <-
c(aliases, list(Rd_aliases(package, lib.loc = lib.loc)))
db <- .build_Rd_xref_db(package, lib.loc = lib.loc)
}
else {
aliases <-
c(aliases, list(Rd_aliases(dir = dir)))
db <- .build_Rd_xref_db(dir = dir)
}
## Flatten the xref db into one big matrix.
db <- cbind(do.call("rbind", db), rep(names(db), sapply(db, NROW)))
## Take the targets from the non-anchored xrefs.
db <- db[db[, 2] == "", -2, drop = FALSE]
## The bad ones:
db <- db[! db[, 1] %in% unlist(aliases), , drop = FALSE]
structure(split(db[, 1], db[, 2]), class = "check_Rd_xrefs")
}
print.check_Rd_xrefs <-
function(x, ...)
{
if(length(x) > 0) {
for(i in seq_along(x)) {
writeLines(gettextf("Missing link(s) in documentation object '%s':",
names(x)[i]))
.pretty_print(x[[i]])
writeLines("")
}
##
## Add some explanatory message and a pointer to R-exts
## eventually ...
##
}
x
}
### * as.alist.call
as.alist.call <-
function(x)
{
y <- as.list(x)
ind <- if(is.null(names(y)))
seq_along(y)
else
which(names(y) == "")
if(any(ind)) {
names(y)[ind] <- sapply(y[ind],as.character)
y[ind] <- rep.int(list(alist(irrelevant = )[[1]]), length(ind))
}
y
}
### * as.alist.symbol
as.alist.symbol <-
function(x)
{
as.alist.call(call(as.character(x)))
}
### * .arg_names_from_call
.arg_names_from_call <-
function(x)
{
y <- as.character(x)
if(!is.null(nx <- names(x))) {
ind <- which(nx != "")
y[ind] <- nx[ind]
}
y
}
### * .functions_to_be_ignored_from_usage
.functions_to_be_ignored_from_usage <-
function(package_name)
{
c("<-", "=",
if(package_name == "base")
c("(", "{", "function", "if", "for", "while", "repeat",
"Math", "Ops", "Summary", "Complex"),
if(package_name == "utils") "?",
if(package_name == "methods") "@")
}
### * .functions_with_no_useful_S3_method_markup
.functions_with_no_useful_S3_method_markup <-
function()
{
## Once upon a time ... there was no useful markup for S3 methods
## for subscripting/subassigning and binary operators. There is
## still no such markup for *unary* operators, and, strictly
## speaking, for S3 Ops group methods for binary operators [but it
## seems that people do not want to provide explicit documentation
## for these].
##
## Support for S3 methods for subscripting/subassigning was added
## for R 2.1, and for S3 methods for binary operators in 2.2.
## Markup for the former is a bit controversial, as some legacy docs
## have non-synopsis-style \usage entries for these methods. E.g.,
## as of 2005-05-21, \link[base]{Extract.data.frame} has
## x[i]
## x[i] <- value
## x[i, j, drop = TRUE]
## x[i, j] <- value
## Hence, we provide internal environment variables for controlling
## what should be ignored.
c(if(!identical(as.logical(Sys.getenv("_R_CHECK_RD_USAGE_METHOD_SUBSET_")),
TRUE))
c("[", "[[", "$", "[<-", "[[<-", "$<-"),
if(identical(as.logical(Sys.getenv("_R_CHECK_RD_USAGE_METHOD_BINOPS_")),
FALSE))
c("+", "-", "*", "/", "^", "<", ">", "<=", ">=", "!=", "==", "%%",
"%/%", "&", "|"),
## Current, nothing for unary operators.
"!")
}
### * .is_call_from_replacement_function_usage
.is_call_from_replacement_function_usage <-
function(x)
{
((length(x) == 3)
&& (identical(x[[1]], as.symbol("<-")))
&& (length(x[[2]]) > 1)
&& is.symbol(x[[3]]))
}
### * .make_signatures
.make_signatures <-
function(cls)
{
## Note that (thanks JMC), when comparing signatures, the signature
## has to be stripped of trailing "ANY" elements (which are always
## implicit) or padded to a fixed length.
sub("(#ANY)*$", "", unlist(lapply(cls, paste, collapse = "#")))
}
### * .package_env
.package_env <-
function(package_name)
{
as.environment(paste("package", package_name, sep = ":"))
}
### * .parse_text_as_much_as_possible
.parse_text_as_much_as_possible <-
function(txt)
{
exprs <- try(parse(text = txt), silent = TRUE)
if(!inherits(exprs, "try-error")) return(exprs)
exprs <- expression()
lines <- unlist(strsplit(txt, "\n"))
bad_lines <- character()
while((n <- length(lines)) > 0) {
i <- 1; txt <- lines[1]
while(inherits(yy <- try(parse(text = txt), silent = TRUE),
"try-error")
&& (i < n)) {
i <- i + 1; txt <- paste(txt, lines[i], collapse = "\n")
}
if(inherits(yy, "try-error")) {
bad_lines <- c(bad_lines, lines[1])
lines <- lines[-1]
}
else {
exprs <- c(exprs, yy)
lines <- lines[-seq_len(i)]
}
}
attr(exprs, "bad_lines") <- bad_lines
exprs
}
### * .parse_usage_as_much_as_possible
.parse_usage_as_much_as_possible <-
function(txt)
{
txt <- gsub("\\\\l?dots", "...", txt)
txt <- gsub("\\\\%", "%", txt)
txt <- .Rd_transform_command(txt, "special", function(u) NULL)
txt <- gsub(.S3_method_markup_regexp, "\"\\\\\\1\"", txt)
txt <- gsub(.S4_method_markup_regexp, "\"\\\\\\1\"", txt)
## Transform <> style markup so that we can catch and
## throw it, rather than "basically ignore" it by putting it in the
## bad_lines attribute.
txt <- gsub("(<>?)", "`\\1`", txt)
.parse_text_as_much_as_possible(txt)
}
### * .pretty_print
.pretty_print <-
function(x)
{
writeLines(strwrap(paste(x, collapse = " "),
indent = 2, exdent = 2))
}
### * .transform_S3_method_markup
.transform_S3_method_markup <-
function(x)
{
## Note how we deal with S3 replacement methods found.
## These come out named "\method{GENERIC}{CLASS}<-" which we
## need to turn into 'GENERIC<-.CLASS'.
sub(sprintf("%s(<-)?", .S3_method_markup_regexp),
"\\3\\5.\\4",
x)
}
### * .S3_method_markup_regexp
## For matching \(S3)?method{GENERIC}{CLASS}.
## GENERIC can be
## * a syntactically valid name
## * one of $ [ [[
## * one of the binary operators
## + - * / ^ < <= > >= != == | & %something%
## (as supported by Rdconv).
## See also .functions_with_no_useful_S3_method_markup.
.S3_method_markup_regexp <-
sprintf("(\\\\(S3)?method\\{(%s)\\}\\{(%s)\\})",
paste(c("[._[:alnum:]]*",
## Subscripting
"\\$", "\\[\\[?",
## Binary operators
"\\+", "\\-", "\\*", "\\/", "\\^", "<=?", ">=?",
"!=", "==", "\\&", "\\|",
"\\%[[:alnum:][:punct:]]*\\%"),
collapse = "|"),
"[._[:alnum:]]*")
### * .S4_method_markup_regexp
## For matching \S4method{GENERIC}{SIGLIST}.
.S4_method_markup_regexp <-
sprintf("(\\\\S4method\\{(%s)\\}\\{(%s)\\})",
"[._[:alnum:]]*",
"[._[:alnum:],]*")
### * .check_package_subdirs
.check_package_subdirs <- function(dir, doDelete = FALSE)
{
OS_subdirs <- c("unix", "windows")
mydir <- function(dir)
{
d <- list.files(dir, all.files = TRUE, full.names = FALSE)
if(!length(d)) return(d)
if(basename(dir) %in% c("R", "man"))
for(os in OS_subdirs) {
os_dir <- file.path(dir, os)
if(file_test("-d", os_dir))
d <- c(d,
file.path(os,
list.files(os_dir,
all.files = TRUE,
full.names = FALSE)))
}
d[sapply(file.path(dir, d), function(x) file_test("-f", x))]
}
if(!file_test("-d", dir))
stop(gettextf("directory '%s' does not exist", dir), domain = NA)
else
dir <- file_path_as_absolute(dir)
wrong_things <- list(R = character(0), man = character(0),
demo = character(0), `inst/doc` = character(0))
code_dir <- file.path(dir, "R")
if(file_test("-d", code_dir)) {
all_files <- mydir(code_dir)
## Under Windows, need a Makefile for methods.
R_files <- c("sysdata.rda", "Makefile.win",
list_files_with_type(code_dir, "code",
full.names = FALSE,
OS_subdirs = OS_subdirs))
wrong <- all_files %w/o% R_files
## now configure might generate files in this directory
generated <- grep("\\.in$", wrong)
if(length(generated)) wrong <- wrong[-generated]
if(length(wrong)) {
wrong_things$R <- wrong
if(doDelete) unlink(file.path(dir, "R", wrong))
}
}
man_dir <- file.path(dir, "man")
if(file_test("-d", man_dir)) {
all_files <- mydir(man_dir)
man_files <- list_files_with_type(man_dir, "docs",
full.names = FALSE,
OS_subdirs = OS_subdirs)
wrong <- all_files %w/o% man_files
if(length(wrong)) {
wrong_things$man <- wrong
if(doDelete) unlink(file.path(dir, "man", wrong))
}
}
demo_dir <- file.path(dir, "demo")
if(file_test("-d", demo_dir)) {
all_files <- mydir(demo_dir)
demo_files <- list_files_with_type(demo_dir, "demo",
full.names = FALSE)
wrong <- all_files %w/o% c("00Index", demo_files)
if(length(wrong)) {
wrong_things$demo <- wrong
if(doDelete) unlink(file.path(dir, "demo", wrong))
}
}
vign_dir <- file.path(dir, "inst", "doc")
if(file_test("-d", vign_dir)) {
vignettes <- list_files_with_type(vign_dir, "vignette",
full.names = FALSE)
vignettes <- c(vignettes,
list_files_with_exts(vign_dir, "pdf",
full.names = FALSE))
## Assume here this is run in the C locale, as it is by R CMD
## check.
OK <- grep("^[[:alpha:]][[:alnum:]._-]+$", vignettes)
wrong <- vignettes
if(length(OK)) wrong <- wrong[-OK]
if(length(wrong)) wrong_things$`inst/doc` <- wrong
}
class(wrong_things) <- "subdir_tests"
wrong_things
}
print.subdir_tests <-
function(x, ...)
{
for(i in which(sapply(x, length) > 0)) {
tag <- names(x)[i]
writeLines(sprintf("Subdirectory '%s' contains invalid file names:",
names(x)[i]))
.pretty_print(x[[i]])
}
invisible(x)
}
### * .check_package_ASCII_code
.check_package_ASCII_code <-
function(dir, respect_quotes = FALSE)
{
OS_subdirs <- c("unix", "windows")
if(!file_test("-d", dir))
stop(gettextf("directory '%s' does not exist", dir), domain = NA)
else
dir <- file_path_as_absolute(dir)
code_dir <- file.path(dir, "R")
wrong_things <- character(0)
if(file_test("-d", code_dir)) {
R_files <- list_files_with_type(code_dir, "code",
full.names = FALSE,
OS_subdirs = OS_subdirs)
for(f in R_files) {
text <- readLines(file.path(code_dir, f), warn = FALSE)
if(.Call(check_nonASCII, text, !respect_quotes))
wrong_things <- c(wrong_things, f)
}
}
if(length(wrong_things)) cat(wrong_things, sep="\n")
invisible(wrong_things)
}
### * .check_package_code_syntax
.check_package_code_syntax <-
function(dir)
{
for(f in list_files_with_type(dir, "code",
OS_subdirs = c("unix", "windows")))
tryCatch(parse(f),
error = function(e)
writeLines(c(sprintf("File '%s':", f),
sprintf(" %s",
unlist(strsplit(conditionMessage(e),
"\n"))))))
}
### * .check_package_code_shlib
.check_package_code_shlib <-
function(dir) {
##
## This is very similar to what happens with checkTnF() etc.
## We should really have a more general-purpose tree walker.
##
## Workhorse function.
filter <- function(file) {
matches <- list()
walker <- function(e) {
if((length(e) > 1)
&& is.call(e)
&& as.character(e[[1]]) %in% c("library.dynam",
"library.dynam.unload")
&& is.character(e[[2]])
&& (regexpr("\\.(so|sl|dll)$", e[[2]]) > -1)
)
matches <<- c(matches, list(e))
if(is.recursive(e))
for(i in seq_along(e)) Recall(e[[i]])
}
exprs <- parse(file)
for(i in seq_along(exprs)) walker(exprs[[i]])
matches
}
code_files <-
list_files_with_type(dir, "code",
OS_subdirs = c("unix", "windows"))
x <- lapply(code_files, filter)
names(x) <- code_files
x <- x[sapply(x, length) > 0]
## Because we really only need this for calling from R CMD check, we
## produce output here in case we found something.
for(fname in names(x)) {
writeLines(gettextf("File '%s':", fname))
xfname <- x[[fname]]
for(i in seq_along(xfname)) {
writeLines(strwrap(gettextf("found %s",
paste(deparse(xfname[[i]]),
collapse = "")),
indent = 2, exdent = 4))
}
}
invisible(x)
}
### * .check_packages_used
.check_packages_used <-
function(package, dir, lib.loc = NULL)
{
## Argument handling.
if(!missing(package)) {
if(length(package) != 1)
stop("argument 'package' must be of length 1")
dir <- .find.package(package, lib.loc)
## Using package installed in @code{dir} ...
code_dir <- file.path(dir, "R")
if(!file_test("-d", code_dir))
stop(gettextf("directory '%s' does not contain R code",
dir),
domain = NA)
if(basename(dir) != "base")
.load_package_quietly(package, lib.loc)
code_env <- if(packageHasNamespace(package, dirname(dir)))
asNamespace(package)
else
.package_env(package)
dfile <- file.path(dir, "DESCRIPTION")
db <- .read_description(dfile)
}
else if(!missing(dir)) {
## Using sources from directory @code{dir} ...
if(!file_test("-d", dir))
stop(gettextf("directory '%s' does not exist", dir),
domain = NA)
else
dir <- file_path_as_absolute(dir)
dfile <- file.path(dir, "DESCRIPTION")
db <- .read_description(dfile)
## we need to check for a bundle here
## Need this to handle bundles ...
contains <- .get_contains_from_package_db(db)
if(length(contains)) {
file <- tempfile()
on.exit(unlink(file))
if(!file.create(file)) stop("unable to create ", file)
for(pkg in contains) {
code_dir <- file.path(dir, pkg, "R")
if(file_test("-d", code_dir)) {
if(!all(.file_append_ensuring_LFs(file,
list_files_with_type(code_dir,
"code"))))
stop("unable to write code files")
}
}
} else {
code_dir <- file.path(dir, "R")
if(file_test("-d", code_dir)) {
file <- tempfile()
on.exit(unlink(file))
if(!file.create(file)) stop("unable to create ", file)
if(!all(.file_append_ensuring_LFs(file,
list_files_with_type(code_dir,
"code"))))
stop("unable to write code files")
} else {
return(invisible())
}
}
}
pkg_name <- db["Package"]
depends <- .get_requires_from_package_db(db, "Depends")
imports <- .get_requires_from_package_db(db, "Imports")
suggests <- .get_requires_from_package_db(db, "Suggests")
enhances <- .get_requires_from_package_db(db, "Enhances")
## Need this to handle bundles ...
contains <- .get_contains_from_package_db(db)
## it is OK to refer to yourself and non-S4 standard packages
standard_package_names <-
.get_standard_package_names()$base %w/o% c("methods", "stats4")
depends_suggests <- c(depends, suggests, pkg_name, contains,
standard_package_names)
imports <- c(imports, depends_suggests, enhances)
## the first argument could be named, or could be a variable name.
## we just have a stop list here.
common_names <- c("pkg", "pkgName", "package", "pos")
bad_exprs <- character()
bad_imports <- character()
uses_methods <- FALSE
find_bad_exprs <- function(e) {
if(is.call(e) || is.expression(e)) {
Call <- deparse(e[[1]])[1]
if(length(e) >= 2) pkg <- deparse(e[[2]])
if(Call %in% c("library", "requires")) {
## Zelig has library()
if(length(e) >= 2) {
pkg <- sub('^"(.*)"$', '\\1', pkg)
## could be inside substitute or a variable
## and is in e.g. R.oo
if(! pkg %in% c(depends_suggests, common_names))
bad_exprs <<- c(bad_exprs, pkg)
}
} else if(Call %in% "::") {
## fathom out if this package has a namespace
if(! pkg %in% imports)
bad_imports <<- c(bad_imports, pkg)
} else if(Call %in% ":::") {
if(! pkg %in% imports)
bad_imports <<- c(bad_imports, pkg)
} else if(Call %in% c("setClass", "setMethod")) {
uses_methods <<- TRUE
}
for(i in seq_along(e)) Recall(e[[i]])
}
}
if(!missing(package)) {
exprs <- lapply(ls(envir = code_env, all.names = TRUE),
function(f) {
f <- get(f, envir = code_env)
if(typeof(f) == "closure")
body(f)
else
NULL
})
if(.isMethodsDispatchOn()) {
## Also check the code in S4 methods.
## This may find things twice.
for(f in methods::getGenerics(code_env)) {
meths <-
methods::linearizeMlist(methods::getMethodsMetaData(f, code_env))
bodies <- lapply(methods::slot(meths, "methods"), body)
## Exclude methods inherited from the 'appropriate'
## parent environment.
##
## Keep this in sync with similar code in undoc().
## Note that direct comparison of
## lapply(methods::slot(meths, "methods"), environment)
## to code_env is not quite right ...
penv <- .Internal(getRegisteredNamespace(as.name(package)))
if(is.environment(penv))
penv <- parent.env(penv)
else
penv <- parent.env(code_env)
if((f %in% methods::getGenerics(penv))
&& !is.null(mlist_from_penv <-
methods::getMethodsMetaData(f, penv))) {
classes_from_cenv <-
methods::slot(meths, "classes")
classes_from_penv <-
methods::slot(methods::linearizeMlist(mlist_from_penv),
"classes")
ind <- is.na(match(.make_signatures(classes_from_cenv),
.make_signatures(classes_from_penv)))
bodies <- bodies[ind]
}
##
exprs <- c(exprs, bodies)
}
}
}
else {
exprs <- try(parse(file = file, n = -1))
if(inherits(exprs, "try-error"))
stop(gettextf("parse error in file '%s'", file),
domain = NA)
}
for(i in seq_along(exprs)) find_bad_exprs(exprs[[i]])
methods_message <-
if(uses_methods && !"methods" %in% c(depends, imports))
gettext("package 'methods' is used but not declared")
else ""
res <- list(others = unique(bad_exprs),
imports = unique(bad_imports),
methods_message = methods_message)
class(res) <- "check_packages_used"
res
}
print.check_packages_used <-
function(x, ...)
{
if(length(x$imports) > 0) {
writeLines(gettext("'::' or ':::' imports not declared from:"))
.pretty_print(x$imports)
}
if(length(x$others) > 0) {
writeLines(gettext("'library' or 'require' calls not declared from:"))
.pretty_print(x$others)
}
if(nchar(x$methods_message))
writeLines(x$methods_message)
invisible(x)
}
### * .check_T_and_F
## T and F checking, next generation.
##
## What are we really trying to do?
##
## In R, T and F are "just" variables which upon startup are bound to
## TRUE and FALSE, respectively, in the base package/namespace. Hence,
## if code uses "global" variables T and F and dynamic lookup is in
## place (for packages, if they do not have a namespace), there may be
## trouble in case T or F were redefined. So we'd like to warn about
## these cases.
##
## A few things to note:
## * Package code top-level bindings *to* T and F are not a problem for
## packages installed for lazy-loading (as the top-level T and F get
## evaluated "appropriately" upon installation.
## * Code in examples using "global" T and F is always a problem, as
## this is evaluated in the global envionment by examples().
## * There is no problem with package code using T and F as local
## variables.
##
## Our current idea is the following. Function findGlobals() in
## codetools already provides a way to (approximately) determine the
## globals. So we can try to get these and report them.
##
## Note that findGlobals() only works on closures, so we definitely miss
## top-level assignments to T or F. This could be taken care of rather
## easily, though.
##
## Note also that we'd like to help people find where the offending
## globals were found. Seems that codetools currently does not offer a
## way of recording e.g. the parent expression, so we do our own thing
## based on the legacy checkTnF code.
.check_T_and_F <-
function(package, dir, lib.loc = NULL)
{
bad_closures <- character()
bad_examples <- character()
find_bad_closures <- function(env) {
objects_in_env <- objects(env, all = TRUE)
x <- lapply(objects_in_env,
function(o) {
v <- get(o, env = env)
if (typeof(v) == "closure")
codetools::findGlobals(v)
})
objects_in_env[sapply(x,
function(s) any(s %in% c("T", "F")))]
}
find_bad_examples <- function(txts) {
env <- new.env()
x <- lapply(txts,
function(txt) {
eval(parse(text =
paste("FOO <- function() {",
paste(txt, collapse = "\n"),
"}",
collapse = "\n")),
env)
find_bad_closures(env)
})
names(txts)[sapply(x, length) > 0]
}
if(!missing(package)) {
if(length(package) != 1)
stop("argument 'package' must be of length 1")
dir <- .find.package(package, lib.loc)
if((package != "base")
&& !packageHasNamespace(package, dirname(dir))) {
.load_package_quietly(package, lib.loc)
code_env <- .package_env(package)
bad_closures <- find_bad_closures(code_env)
}
example_texts <-
.get_example_texts_from_example_dir(file.path(dir, "R-ex"))
}
else {
## The dir case.
if(missing(dir))
stop("you must specify 'package' or 'dir'")
dir <- file_path_as_absolute(dir)
code_dir <- file.path(dir, "R")
if(!packageHasNamespace(basename(dir), dirname(dir))
&& file_test("-d", code_dir)) {
code_env <- new.env()
.source_assignments_in_code_dir(code_dir, code_env)
bad_closures <- find_bad_closures(code_env)
}
example_texts <- .get_example_texts_from_source_dir(dir)
}
bad_examples <- find_bad_examples(example_texts)
out <- list(bad_closures = bad_closures,
bad_examples = bad_examples)
class(out) <- "check_T_and_F"
out
}
.get_example_texts_from_example_dir <-
function(dir)
{
if(!file_test("-d", dir)) return(NULL)
files <- list_files_with_exts(dir, "R")
texts <- lapply(files,
function(f) paste(readLines(f, warn = FALSE),
collapse = "\n"))
names(texts) <- files
texts
}
.get_example_texts_from_source_dir <-
function(dir)
{
if(!file_test("-d", file.path(dir, "man"))) return(NULL)
sapply(Rd_db(dir = dir),
function(s) {
.get_Rd_example_code(paste(Rd_pp(s), collapse = "\n"))
})
}
print.check_T_and_F <- function(x, ...) {
if(length(x$bad_closures)) {
msg <- gettext("Found possibly global T or F in the following functions:")
writeLines(strwrap(msg))
.pretty_print(x$bad_closures)
}
if(length(x$bad_examples)) {
msg <- gettext("Found possibly global T or F in the following Rd example files:")
writeLines(strwrap(msg))
writeLines(paste(" ", x$bad_examples))
}
invisible(x)
}
### Local variables: ***
### mode: outline-minor ***
### outline-regexp: "### [*]+" ***
### End: ***