diff --git a/.lintr b/.lintr index fbe54d2ce..8420e7ba5 100644 --- a/.lintr +++ b/.lintr @@ -1,5 +1,5 @@ linters: all_linters( - backport_linter("3.6.0", except = c("R_user_dir", "deparse1", "...names")), + backport_linter("auto", except = c("R_user_dir", "deparse1", "...names")), line_length_linter(120L), object_overwrite_linter(allow_names = c("line", "lines", "pipe", "symbols")), todo_comment_linter( diff --git a/R/backport_linter.R b/R/backport_linter.R index 546463072..b652b4a45 100644 --- a/R/backport_linter.R +++ b/R/backport_linter.R @@ -2,9 +2,11 @@ #' #' Check for usage of unavailable functions. Not reliable for testing r-devel dependencies. #' -#' @param r_version Minimum R version to test for compatibility. Defaults to -#' the R version currently in use. The version can be specified as a version -#' number, or as a version alias (such as `"devel"`, `"oldrel"`, `"oldrel-1"`). +#' @param r_version Minimum R version to test for compatibility. The version +#' can be specified as a version number, or as a version alias (such as `"devel"`, +#' `"oldrel"`, `"oldrel-1"`). It can also be `"auto"` (the default) to use the minimum R version +#' declared in the `DESCRIPTION` file of R packages, and the R version in the current +#' R session outside of packages. #' @param except Character vector of functions to be excluded from linting. #' Use this to list explicitly defined backports, e.g. those imported from the `{backports}` package or manually #' defined in your package. @@ -46,7 +48,7 @@ #' @evalRd rd_tags("backport_linter") #' @seealso [linters] for a complete list of linters available in lintr. #' @export -backport_linter <- function(r_version = getRversion(), except = character()) { +backport_linter <- function(r_version = "auto", except = character()) { r_version <- normalize_r_version(r_version) if (all(r_version >= R_system_version(names(backports)))) { @@ -120,6 +122,15 @@ normalize_r_version <- function(r_version) { )) r_version <- R_system_version(available_patches[selected_patch]) + } else if (identical(r_version, "auto")) { + r_version <- min_r_version() + if (is.na(r_version)) { + cli_warn(c( + x = "No DESCRIPTION file or no specified minimum R version could be found.", + i = "Setting {.arg r_version} to the currently running R version (R {getRversion()})." + )) + r_version <- getRversion() + } } else if (is.character(r_version)) { r_version <- R_system_version(r_version, strict = TRUE) } else if (!inherits(r_version, "R_system_version")) { diff --git a/R/settings_utils.R b/R/settings_utils.R index 166dd362a..7a9be2784 100644 --- a/R/settings_utils.R +++ b/R/settings_utils.R @@ -112,3 +112,23 @@ pkg_name <- function(path = find_package()) { nm <- read.dcf(file.path(path, "DESCRIPTION"), fields = "Package")[1L] if (!is.na(nm)) nm } + +min_r_version <- function(path = find_package(".")) { + if (is.null(path)) { + return(NA_character_) + } + depends_field <- read.dcf(file.path(path, "DESCRIPTION"), fields = "Depends") + + depends <- trimws(strsplit(depends_field, ",", fixed = TRUE)[[1L]]) + + min_r_ver <- gsub("^R \\(>=?\\s(.+)\\)", "\\1", grep("R ", depends, value = TRUE, fixed = TRUE)) + + if (length(min_r_ver) == 0L) { + return(NA_character_) + } + # According to 'Writing R Extensions', the trailing 0 for patch version can + # be dropped. + # But we want to identically match an existing version number so we add it if + # it's missing. + gsub("^(\\d+\\.\\d+)$", "\\1.0", min_r_ver) +} diff --git a/man/backport_linter.Rd b/man/backport_linter.Rd index 22c97ebbe..09616adb3 100644 --- a/man/backport_linter.Rd +++ b/man/backport_linter.Rd @@ -4,12 +4,14 @@ \alias{backport_linter} \title{Backport linter} \usage{ -backport_linter(r_version = getRversion(), except = character()) +backport_linter(r_version = "auto", except = character()) } \arguments{ -\item{r_version}{Minimum R version to test for compatibility. Defaults to -the R version currently in use. The version can be specified as a version -number, or as a version alias (such as \code{"devel"}, \code{"oldrel"}, \code{"oldrel-1"}).} +\item{r_version}{Minimum R version to test for compatibility. The version +can be specified as a version number, or as a version alias (such as \code{"devel"}, +\code{"oldrel"}, \code{"oldrel-1"}). It can also be \code{"auto"} (the default) to use the minimum R version +declared in the \code{DESCRIPTION} file of R packages, and the R version in the current +R session outside of packages.} \item{except}{Character vector of functions to be excluded from linting. Use this to list explicitly defined backports, e.g. those imported from the \code{{backports}} package or manually diff --git a/tests/testthat/dummy_packages/auto_backport_fail/DESCRIPTION b/tests/testthat/dummy_packages/auto_backport_fail/DESCRIPTION new file mode 100644 index 000000000..bccb4eb31 --- /dev/null +++ b/tests/testthat/dummy_packages/auto_backport_fail/DESCRIPTION @@ -0,0 +1,3 @@ +Package: mypkg +Version: 0.1.0 +Depends: R (>= 4.0.0) \ No newline at end of file diff --git a/tests/testthat/dummy_packages/auto_backport_fail/R/my_numtobits.R b/tests/testthat/dummy_packages/auto_backport_fail/R/my_numtobits.R new file mode 100644 index 000000000..4ccb43c0b --- /dev/null +++ b/tests/testthat/dummy_packages/auto_backport_fail/R/my_numtobits.R @@ -0,0 +1,3 @@ +my_numtobits <- function() { + numToBits(2) +} diff --git a/tests/testthat/dummy_packages/auto_backport_pass/DESCRIPTION b/tests/testthat/dummy_packages/auto_backport_pass/DESCRIPTION new file mode 100644 index 000000000..487b50b48 --- /dev/null +++ b/tests/testthat/dummy_packages/auto_backport_pass/DESCRIPTION @@ -0,0 +1,3 @@ +Package: mypkg +Version: 0.1.0 +Depends: R (>= 4.2.0) \ No newline at end of file diff --git a/tests/testthat/dummy_packages/auto_backport_pass/R/my_numtobits.R b/tests/testthat/dummy_packages/auto_backport_pass/R/my_numtobits.R new file mode 100644 index 000000000..4ccb43c0b --- /dev/null +++ b/tests/testthat/dummy_packages/auto_backport_pass/R/my_numtobits.R @@ -0,0 +1,3 @@ +my_numtobits <- function() { + numToBits(2) +} diff --git a/tests/testthat/test-backport_linter.R b/tests/testthat/test-backport_linter.R index 9b2d47bfe..b20305627 100644 --- a/tests/testthat/test-backport_linter.R +++ b/tests/testthat/test-backport_linter.R @@ -10,7 +10,8 @@ test_that("backport_linter detects backwards-incompatibility", { expect_no_lint(".getNamespaceInfo(dir.exists(lapply(x, toTitleCase)))", backport_linter()) expect_no_lint(".getNamespaceInfo(dir.exists(lapply(x, toTitleCase)))", backport_linter("release")) expect_no_lint(".getNamespaceInfo(dir.exists(lapply(x, toTitleCase)))", backport_linter("devel")) - + expect_no_lint(".getNamespaceInfo(dir.exists(lapply(x, toTitleCase)))", backport_linter("auto")) + expect_lint( "numToBits(2)", rex::rex("numToBits (R 4.1.0) is not always available for requested dependency (R >= 4.0.0)."), @@ -82,3 +83,46 @@ test_that("backport_linter generates expected warnings", { ) expect_identical(l, lint(tmp, backport_linter("3.0.0"))) }) + +test_that("backport_linter works with package R version", { + pkg_lints_from_outside <- lint_package( + "dummy_packages/auto_backport_fail", + linters = backport_linter(), + parse_settings = FALSE + ) + pkg_lints_from_root <- withr::with_dir( + "dummy_packages/auto_backport_fail", + lint_package(linters = backport_linter(), parse_settings = FALSE) + ) + + expect_length(pkg_lints_from_outside, 1L) + expect_identical( + pkg_lints_from_outside[[1]]$message, + "numToBits (R 4.1.0) is not always available for requested dependency (R >= 4.0.0)." + ) + expect_length(pkg_lints_from_root, 1L) + expect_identical( + pkg_lints_from_root[[1]]$message, + "numToBits (R 4.1.0) is not always available for requested dependency (R >= 4.0.0)." + ) + + # Can be overwritten + pkg_lints_from_outside_noauto <- lint_package( + "dummy_packages/auto_backport_fail", + backport_linter("3.0.0"), + parse_settings = FALSE + ) + + expect_length(pkg_lints_from_outside_noauto, 1L) + expect_identical( + pkg_lints_from_outside_noauto[[1]]$message, + "numToBits (R 4.1.0) is not always available for requested dependency (R >= 3.0.0)." + ) + + pkg_lints_auto_pass <- lint_package( + "dummy_packages/auto_backport_pass", + linters = backport_linter(), + parse_settings = FALSE + ) + expect_length(pkg_lints_auto_pass, 0L) +})