Skip to content

Commit 9be262c

Browse files
authored
adapt backport linter to R 4.1.0 and make versioning more robust (#974)
* adapt backport linter to R 4.1.0 and make versioning more robust * incorporate feedback * use logical indexing instead of tricky head() call, rename variable to clarify purpose
1 parent c7eb033 commit 9be262c

File tree

2 files changed

+65
-13
lines changed

2 files changed

+65
-13
lines changed

R/backport_linter.R

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,22 @@
77
#' @seealso [linters] for a complete list of linters available in lintr.
88
#' @export
99
backport_linter <- function(r_version = getRversion()) {
10+
r_version <- normalize_r_version(r_version)
11+
1012
Linter(function(source_file) {
11-
if (inherits(r_version, "numeric_version")) r_version <- format(r_version)
12-
if (r_version < "3.0.0") {
13-
warning("It is not recommended to depend on an R version older than 3.0.0. Resetting 'r_version' to 3.0.0.")
14-
r_version <- "3.0.0"
15-
}
1613
if (is.null(source_file$xml_parsed_content)) return(list())
14+
if (all(r_version >= R_system_version(names(backports)))) return(list())
1715

1816
xml <- source_file$xml_parsed_content
1917

2018
names_xpath <- "//*[self::SYMBOL or self::SYMBOL_FUNCTION_CALL]"
2119
all_names_nodes <- xml2::xml_find_all(xml, names_xpath)
2220
all_names <- xml2::xml_text(all_names_nodes)
2321

24-
# guaranteed to include 1 by early return above; which.min fails if all TRUE (handled by nomatch)
25-
needs_backport_names <- backports[1:(match(FALSE, r_version < names(backports), nomatch = length(backports)) - 1L)]
22+
backport_blacklist <- backports[r_version < R_system_version(names(backports))]
2623

2724
# not sapply/vapply, which may over-simplify to vector -- cbind makes sure we have a matrix so rowSums works
28-
needs_backport <- do.call(cbind, lapply(needs_backport_names, function(nm) all_names %in% nm))
25+
needs_backport <- do.call(cbind, lapply(backport_blacklist, function(nm) all_names %in% nm))
2926
bad_idx <- rowSums(needs_backport) > 0L
3027

3128
lapply(which(bad_idx), function(ii) {
@@ -42,7 +39,7 @@ backport_linter <- function(r_version = getRversion()) {
4239
type = "warning",
4340
message = sprintf(
4441
"%s (R %s) is not available for dependency R >= %s.",
45-
all_names[ii], names(needs_backport_names)[which(needs_backport[ii, ])], r_version
42+
all_names[ii], names(backport_blacklist)[which(needs_backport[ii, ])], r_version
4643
),
4744
line = source_file$lines[[line1]],
4845
ranges = list(c(col1, col2))
@@ -51,8 +48,45 @@ backport_linter <- function(r_version = getRversion()) {
5148
})
5249
}
5350

51+
normalize_r_version <- function(r_version) {
52+
if (is.character(r_version) &&
53+
re_matches(r_version, rex(start, "release" %or%
54+
list("oldrel", maybe("-", digits)) %or%
55+
"devel", end))) {
56+
# Support devel, release, oldrel, oldrel-1, ...
57+
58+
all_versions <- names(backports)
59+
minor_versions <- unique(re_substitutes(all_versions, rex(".", digits, end), ""))
60+
version_names <- c("devel", "release", "oldrel", paste0("oldrel-", seq_len(length(minor_versions) - 3L)))
61+
if (!r_version %in% version_names) {
62+
# This can only trip if e.g. oldrel-99 is requested
63+
stop("`r_version` must be a version number or one of ", toString(sQuote(version_names)))
64+
}
65+
requested_version <- minor_versions[match(r_version, table = version_names)]
66+
available_patches <- all_versions[startsWith(all_versions, requested_version)]
67+
selected_patch <- which.max(as.integer(
68+
substr(available_patches, start = nchar(requested_version) + 2L, stop = nchar(available_patches))
69+
))
70+
71+
r_version <- R_system_version(available_patches[selected_patch])
72+
} else if (is.character(r_version)) {
73+
r_version <- R_system_version(r_version, strict = TRUE)
74+
} else if (!inherits(r_version, "R_system_version")) {
75+
stop("`r_version` must be a R version number, returned by R_system_version(), or a string.")
76+
}
77+
if (r_version < "3.0.0") {
78+
warning("It is not recommended to depend on an R version older than 3.0.0. Resetting 'r_version' to 3.0.0.")
79+
r_version <- R_system_version("3.0.0")
80+
}
81+
r_version
82+
}
83+
84+
# Sources:
85+
# devel NEWS https://cran.rstudio.com/doc/manuals/r-devel/NEWS.html
86+
# release NEWS https://cran.r-project.org/doc/manuals/r-release/NEWS.html
5487
backports <- list(
55-
`devel` = c("...names", "checkRdContents", "numToBits", "numToInts", "packBits"),
88+
`4.2.0` = c(".pretty", ".LC.categories", "Sys.setLanguage()"), # R devel needs to be ahead of all other versions
89+
`4.1.0` = c("numToBits", "numToInts", "gregexec", "charClass", "checkRdContents", "...names"),
5690
`4.0.0` = c(
5791
".class2", ".S3method", "activeBindingFunction", "deparse1", "globalCallingHandlers",
5892
"infoRDS", "list2DF", "marginSums", "proportions", "R_user_dir", "socketTimeout", "tryInvokeRestart"

tests/testthat/test-backport_linter.R

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
test_that("backport_linter detects backwards-incompatibility", {
22
# default should be current R version; all of these are included on our dependency
33
expect_lint(".getNamespaceInfo(dir.exists(lapply(x, toTitleCase)))", NULL, backport_linter())
4+
expect_lint(".getNamespaceInfo(dir.exists(lapply(x, toTitleCase)))", NULL, backport_linter("release"))
5+
expect_lint(".getNamespaceInfo(dir.exists(lapply(x, toTitleCase)))", NULL, backport_linter("devel"))
46

57
# don't allow dependencies older than we've recorded
68
writeLines("x <- x + 1", tmp <- tempfile())
@@ -11,22 +13,38 @@ test_that("backport_linter detects backwards-incompatibility", {
1113

1214
expect_lint(
1315
"numToBits(2)",
14-
rex("numToBits (R devel) is not available for dependency R >= 4.0.0."),
16+
rex("numToBits (R 4.1.0) is not available for dependency R >= 4.0.0."),
1517
backport_linter("4.0.0")
1618
)
1719
# symbols as well as calls
1820
expect_lint(
1921
"lapply(1:10, numToBits)",
20-
rex("numToBits (R devel) is not available for dependency R >= 4.0.0."),
22+
rex("numToBits (R 4.1.0) is not available for dependency R >= 4.0.0."),
2123
backport_linter("4.0.0")
2224
)
2325

2426
expect_lint(
2527
"trimws(...names())",
2628
list(
2729
rex("trimws (R 3.2.0) is not available for dependency R >= 3.0.0."),
28-
rex("...names (R devel) is not available for dependency R >= 3.0.0.")
30+
rex("...names (R 4.1.0) is not available for dependency R >= 3.0.0.")
2931
),
3032
backport_linter("3.0.0")
3133
)
34+
35+
# oldrel specification
36+
expect_lint(
37+
"numToBits(2)",
38+
rex("numToBits (R 4.1.0) is not available for dependency R >= 4.0.0."),
39+
backport_linter("oldrel")
40+
)
41+
42+
expect_error(backport_linter("oldrel-99"), "`r_version` must be a version number or one of")
43+
44+
# NB: oldrel-1 could be 3.6.3, but we don't have any backports listed for 3.6.3
45+
expect_lint(
46+
"numToBits(2)",
47+
rex("numToBits (R 4.1.0) is not available for dependency R >= 3.6.0."),
48+
backport_linter("oldrel-1")
49+
)
3250
})

0 commit comments

Comments
 (0)