├── .Rbuildignore ├── .gitignore ├── .travis.yml ├── DESCRIPTION ├── NAMESPACE ├── R ├── cycle.R ├── oldie.R ├── retire.R ├── signal-retired.R ├── utils.R └── version.R ├── README.md ├── codecov.yml ├── man ├── oldie-package.Rd ├── promote_retirement.Rd └── retire.Rd ├── oldie.Rproj └── tests ├── testthat.R └── testthat ├── helper-conditions.R ├── helper-versioning.R ├── test-cycle.R ├── test-retire.R ├── test-signal-retired.R └── test-version.R /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^\.travis\.yml$ 4 | ^codecov\.yml$ 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # R for travis: see documentation at https://docs.travis-ci.com/user/languages/r 2 | 3 | language: r 4 | sudo: false 5 | cache: packages 6 | 7 | matrix: 8 | include: 9 | - r: oldrel 10 | - r: release 11 | env: R_CODECOV=true 12 | - r: devel 13 | 14 | after_success: 15 | - if [[ "${R_CODECOV}" ]]; then R -e 'covr::codecov()'; fi 16 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: oldie 2 | Title: Tools for Deprecating and Retiring Functions or Arguments 3 | Version: 0.0.0.9000 4 | Description: A toolbox for marking functions or arguments as 5 | deprecated with different levels of verbosity defined by a release 6 | cycle. 7 | Authors@R: c( 8 | person("Lionel", "Henry", ,"lionel@rstudio.com", c("aut", "cre")), 9 | person("RStudio", role = "cph") 10 | ) 11 | License: GPL-3 12 | URL: https://github.com/r-lib/oldie 13 | BugReports: https://github.com/r-lib/oldie/issues 14 | LazyData: true 15 | ByteCompile: true 16 | Encoding: UTF-8 17 | RoxygenNote: 6.0.1 18 | Roxygen: list(markdown = TRUE) 19 | Depends: 20 | R (>= 3.1) 21 | Imports: 22 | glue, 23 | purrr, 24 | rlang 25 | Suggests: 26 | covr, 27 | testthat 28 | Remotes: 29 | lionel-/rlang@7f969c6 30 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method("[[",version) 4 | S3method("[[<-",version) 5 | S3method(Ops,version) 6 | S3method(as.character,version) 7 | S3method(length,version) 8 | S3method(print,version) 9 | export(demote_retirement) 10 | export(promote_retirement) 11 | export(retire) 12 | export(signal_retired) 13 | import(rlang) 14 | importFrom(glue,glue) 15 | importFrom(purrr,every) 16 | importFrom(purrr,map) 17 | importFrom(purrr,map2) 18 | importFrom(purrr,map_chr) 19 | importFrom(purrr,map_if) 20 | importFrom(purrr,map_lgl) 21 | -------------------------------------------------------------------------------- /R/cycle.R: -------------------------------------------------------------------------------- 1 | 2 | new_cycle <- function(cycle) { 3 | if (!length(cycle) || length(cycle) > 3) { 4 | abort("`cycle` must have 1, 2, or 3 components") 5 | } 6 | if (is_character(cycle)) { 7 | cycle <- chr_as_cycle(cycle) 8 | } 9 | 10 | cycle_check(cycle, n_components = 3, max_digits = 2, minor = FALSE) 11 | 12 | cycle 13 | } 14 | new_cycle_chr <- function(cycle) { 15 | cycle <- new_cycle(cycle) 16 | cycle <- map_if(cycle, is_null, function(...) "") 17 | map_chr(cycle, as.character) 18 | } 19 | 20 | chr_as_cycle <- function(cycle) { 21 | if (all(cycle == "")) { 22 | abort("`cycle` can't be empty") 23 | } 24 | 25 | is_empty <- cycle == "" 26 | cycle[!is_empty] <- map(cycle[!is_empty], ver) 27 | cycle[is_empty] <- list(NULL) 28 | 29 | # All cycles must have 3 components 30 | if (length(cycle) < 3) { 31 | n <- length(cycle) 32 | n_missing <- 3 - n 33 | 34 | filler <- list_len(n_missing) 35 | cycle <- c(cycle, filler) 36 | 37 | for (i in seq_len(n_missing)) { 38 | cycle[[n + i]] <- ver_bump(cycle[[n + i - 1]], "minor") 39 | } 40 | } 41 | 42 | cycle 43 | } 44 | 45 | cycle_check <- function(cycle, n_components, max_digits, minor) { 46 | is_empty <- map_lgl(cycle, is_null) 47 | trimmed_cycle <- cycle[!is_empty] 48 | map(trimmed_cycle, ver_check, n_components, max_digits, minor) 49 | 50 | if (length(trimmed_cycle) > 1) { 51 | if (any(slide_lgl(trimmed_cycle, `>=`))) { 52 | abort("`cycle` versions must be monotonically increasing") 53 | } 54 | } 55 | } 56 | ver_check <- function(ver, n_components = NULL, max_digits = NULL, minor = NULL) { 57 | if (!is_version(ver)) { 58 | abort("can't parse version") 59 | } 60 | 61 | components <- ver_components(ver) 62 | 63 | if (!is_version(ver, n_components = n_components)) { 64 | msg <- "version must have %s components, not %s" 65 | msg <- sprintf(msg, n_components, length(components)) 66 | abort(msg) 67 | } 68 | 69 | if (!is_version(ver, max_digits = max_digits)) { 70 | msg <- "version can't have components with more than %s digits" 71 | msg <- sprintf(msg, max_digits) 72 | abort(msg) 73 | } 74 | 75 | if (!is_version(ver, minor = minor)) { 76 | if (minor) { 77 | abort("version must be a minor update") 78 | } else { 79 | abort("version can't be a minor update") 80 | } 81 | } 82 | 83 | invisible(TRUE) 84 | } 85 | -------------------------------------------------------------------------------- /R/oldie.R: -------------------------------------------------------------------------------- 1 | #' @import rlang 2 | #' @importFrom glue glue 3 | #' @importFrom purrr every map map_chr map_if map_lgl map2 4 | "_PACKAGE" 5 | -------------------------------------------------------------------------------- /R/retire.R: -------------------------------------------------------------------------------- 1 | #' Deprecate a function or argument 2 | #' 3 | #' @description 4 | #' 5 | #' `retire()` marks a function or some of its arguments as 6 | #' obsolete. This enables automatic documentation by roxygen, signals 7 | #' a condition when a deprecated function is run or when a deprecated 8 | #' argument is supplied, and checks that the deprecation cycle 9 | #' conforms to tidyverse rules. 10 | #' 11 | #' The conditions are signalled with with `signal_retired()` which 12 | #' has the same interface as `retire()`. It should always be called 13 | #' directly within the deprecated function. Since it is added 14 | #' automatically by `retire()`, you should rarely have to call it 15 | #' yourself. 16 | #' 17 | #' @section Deprecation levels: 18 | #' 19 | #' There are three deprecation levels: 20 | #' 21 | #' - **Soft-deprecated**: This is the first stage of deprecation. The 22 | #' function or argument continues to work normally without any 23 | #' warning. Soft-deprecated functions will generally not be documented, and 24 | #' should not be used in examples or package code. They are left 25 | #' around so that existing code continues to work, but new code 26 | #' should not use them. 27 | #' 28 | #' To make soft-deprecated functions signal an error, see below. 29 | #' 30 | #' - **Deprecated**: The function or argument now issues a warning 31 | #' when used or supplied. Users should upgrade their code to use the 32 | #' suggested replacement, if any. 33 | #' 34 | #' - **Defunct**: The function or argument now issues an error when 35 | #' used or supplied. 36 | #' 37 | #' These levels are defined by a deprecation cycle, see section 38 | #' below. You can promote the current deprecation level by setting the 39 | #' global option `oldie_verbose_retirement` to `TRUE`. 40 | #' Soft-deprecated functions then become deprecated, deprecated 41 | #' functions become defunct, and so on. This is useful to check 42 | #' whether you're relying on any soft-deprecated functions or 43 | #' arguments. 44 | #' 45 | #' @section Deprecation cycle: 46 | #' 47 | #' `.cycle` associates each deprecation stage to a release version of 48 | #' your package. It should be a character vectors of three versions. 49 | #' 50 | #' * `c("0.1.0", "0.3.0", "1.0.0")`: Soft-deprecation at after the 51 | #' 0.1.0 release, deprecation after 0.3.0, and defunct after 1.0.0. 52 | #' 53 | #' * `"0.1.0"`: This is equivalent to `c("0.1.0", "0.2.0", "0.3.0")`. 54 | #' When a single version is supplied, it is assumed that it marks 55 | #' the start of a deprecation cycle that is bumped at each minor 56 | #' version (middle number). 57 | #' 58 | #' * `c("0.1.0", "", "")`: The function is soft-deprecated but is not 59 | #' planned to ever be deprecated or defunct. This is useful for 60 | #' discouraging users from using a function without forcing them to 61 | #' change their code. 62 | #' 63 | #' @param .fn The function to deprecate or whose arguments are to be 64 | #' deprecated. This should be supplied as a bare name. 65 | #' @param .cycle A character vector defining the deprecation cycle. 66 | #' See the relevant section. 67 | #' @param ... Replacements, supplied as bare names. 68 | #' 69 | #' * If no replacement is supplied, the function is deprecated with no 70 | #' replacement. 71 | #' 72 | #' * If a single unnamed replacement is supplied, the function is 73 | #' deprecated with the replacement. If the replacement function 74 | #' lives in another package, indicate it with a namespace: 75 | #' `"pkg::replacement"`. 76 | #' 77 | #' * If one or several named replacements are supplied, the function 78 | #' is not deprecated. Instead, the supplied arguments are. `old = 79 | #' new` means that the argument `old` is deprecated with replacement 80 | #' `new`. `old = ` means that the argument `old` is deprecated 81 | #' without replacement. 82 | #' @param .msg An alternative deprecation message. 83 | #' @export 84 | #' @examples 85 | #' # Let's create an obsolete function: 86 | #' old_fn <- function() "old" 87 | #' 88 | #' # You can deprecate it without any replacement: 89 | #' retire(old_fn, "0.1.0") 90 | #' 91 | #' # The cycle above specifies only one version. The cycle is 92 | #' # automatically filled and the above expression is thus equivalent to: 93 | #' retire(old_fn, c("0.1.0", "0.2.0", "0.3.0")) 94 | #' 95 | #' # If there is a new function replacing the old one, just supply its 96 | #' # bare name: 97 | #' retire(old_fn, "0.1.0", replacement_fn) 98 | #' 99 | #' 100 | #' # Deprecating an argument is very similar. They are supplied as 101 | #' # key-value pairs where the key is the deprecated argument and the 102 | #' # value, if supplied, is the replacement. This deprecates an 103 | #' # argument without replacement: 104 | #' fn <- function(..., old) NULL 105 | #' retire(fn, "0.1.0", old = ) 106 | #' 107 | #' # This deprecates with replacement. The deprecated argument is 108 | #' # automatically reassigned to the replacement: 109 | #' fn <- function(..., new, old) NULL 110 | #' retire(fn, "0.1.0", old = new) 111 | #' 112 | #' # The deprecated argument will be added to the formals if 113 | #' # needed. This way you can omit the deprecated arguments from the 114 | #' # function declaration: 115 | #' fn <- function(..., new) NULL 116 | #' retire(fn, "0.1.0", old = new) 117 | retire <- function(.fn, .cycle, ..., .msg = NULL) { 118 | nm <- ensym(.fn) 119 | stopifnot(is_closure(.fn)) 120 | 121 | if (is_fn_replacement(...)) { 122 | fn <- retire_function(.fn, nm, .cycle, ..., .msg = .msg) 123 | } else { 124 | fn <- retire_arguments(.fn, nm, .cycle, ..., .msg = .msg) 125 | } 126 | new_fn(fn) 127 | } 128 | is_fn_replacement <- function(...) { 129 | n_dots <- nargs() 130 | 131 | if (!n_dots) { 132 | return(TRUE) 133 | } 134 | 135 | n_dots == 1 && names2(exprs(..., .ignore_empty = "none")) == "" 136 | } 137 | 138 | retire_function <- function(.fn, .name, .cycle, ..., .msg = NULL) { 139 | if (is_retired(.fn)) { 140 | abort(sprintf("Function `%s` is already deprecated", as_string(.name))) 141 | } 142 | 143 | .cycle <- new_cycle_chr(.cycle) 144 | 145 | if (dots_n(...)) { 146 | replacement <- expr(...) 147 | if (!is_symbol(replacement) && !is_namespaced_symbol(replacement)) { 148 | abort("Replacement function must be a symbol") 149 | } 150 | data <- list(.name, .cycle, replacement, .msg = .msg) 151 | } else { 152 | data <- list(.name, .cycle, .msg = .msg) 153 | } 154 | 155 | # Remove NULL arguments for prettier code expansion 156 | if (is_null(.msg)) { 157 | data$.msg <- NULL 158 | } 159 | 160 | body(.fn) <- expr({ 161 | oldie::signal_retired(!!! data) 162 | !!! body(.fn) 163 | }) 164 | 165 | set_attrs(.fn, retired = TRUE) 166 | } 167 | 168 | retire_arguments <- function(.fn, .name, .cycle, ..., .msg = NULL) { 169 | args <- exprs(..., .ignore_empty = "none") 170 | if (!every(args, is_symbol)) { 171 | abort("Replacements must be symbols") 172 | } 173 | 174 | nms <- names2(args) 175 | if (any(nms == "")) { 176 | abort("Replacements must be named") 177 | } 178 | 179 | already_retired <- nms %in% names(retired_args(.fn)) 180 | if (any(already_retired)) { 181 | bad <- nms[already_retired] 182 | has <- pluralise_len(bad, "has", "have") 183 | abort(glue("{ bad_symbols(bad) } { has } already been deprecated")) 184 | } 185 | 186 | replacements <- map_chr(args, as_string) 187 | 188 | .fn <- add_retired_formals(.fn, replacements) 189 | 190 | depr_exprs <- map2(nms, replacements, retired_arg_expr, .name, .cycle) 191 | fn_body(.fn) <- expr({ 192 | !!! depr_exprs 193 | 194 | NULL # Work around quasiquotation bug 195 | 196 | !!! body(.fn) 197 | }) 198 | 199 | .cycle <- new_cycle_chr(.cycle) 200 | retired_args <- map(set_names(replacements, nms), retired_arg, cycle = .cycle) 201 | retired_args <- c(retired_args(.fn), retired_args) 202 | .fn <- set_attrs(.fn, retired_args = retired_args) 203 | 204 | .fn 205 | } 206 | add_retired_formals <- function(fn, replacements) { 207 | formals <- fn_fmls(fn) 208 | formals_nms <- names(formals) 209 | if (!all(replacements %in% c(formals_nms, ""))) { 210 | abort("Can't find replacement in function arguments") 211 | } 212 | 213 | nms <- names(replacements) 214 | existing <- nms %in% formals_nms 215 | 216 | new_args <- set_names(nms[!existing]) 217 | new_args <- map(new_args, function(...) missing_arg()) 218 | fn_fmls(fn) <- c(formals, new_args) 219 | 220 | fn 221 | } 222 | 223 | retired_arg_expr <- function(old, new, name, cycle, body) { 224 | old_sym <- sym(old) 225 | new_sym <- sym(new) 226 | 227 | if (is_missing(new_sym)) { 228 | reassign <- NULL 229 | } else { 230 | reassign <- expr(UQ(new_sym) <- UQ(old_sym)) 231 | } 232 | 233 | expr( 234 | if (!missing(!! old_sym)) { 235 | oldie::signal_retired(!! name, !! cycle, !!! set_names(new, old)) 236 | !!! reassign 237 | } 238 | ) 239 | } 240 | utils::globalVariables("UQ<-") 241 | 242 | retired_arg <- function(replacement, cycle) { 243 | list(replacement = replacement, cycle = cycle) 244 | } 245 | 246 | is_retired <- function(x) { 247 | is_true(attr(x, "retired")) 248 | } 249 | has_retired_args <- function(x) { 250 | length(retired_args(x)) 251 | } 252 | retired_args <- function(x) { 253 | attr(x, "retired_args") 254 | } 255 | -------------------------------------------------------------------------------- /R/signal-retired.R: -------------------------------------------------------------------------------- 1 | #' @rdname retire 2 | #' @export 3 | signal_retired <- function(.fn, .cycle, ..., .msg = NULL) { 4 | name <- as_string(ensym(.fn)) 5 | 6 | caller_fn <- caller_fn() 7 | if (!is_namespace(get_env(caller_fn))) { 8 | abort("Deprecated functions must be scoped in a namespace") 9 | } 10 | 11 | cycle <- new_cycle(.cycle) 12 | pkg_version <- pkg_ver(ns_env_name(caller_fn)) 13 | 14 | level <- deprecation_level(cycle, pkg_version) 15 | effective_level <- maybe_promote_deprecation(level) 16 | 17 | if (!level) { 18 | version <- "`undefined`" 19 | } else { 20 | version <- as.character(cycle[[level]]) 21 | } 22 | 23 | # Return immediately if not yet deprecated 24 | if (effective_level < 1) { 25 | return(invisible(NULL)) 26 | } 27 | 28 | type <- switch(effective_level, 29 | `1` = "soft-deprecated", 30 | `2` = "deprecated", 31 | `3` = "defunct" 32 | ) 33 | signal <- switch(effective_level, 34 | `1` = cnd_signal, 35 | `2` = cnd_warn, 36 | `3` = cnd_abort 37 | ) 38 | 39 | if (is_fn_replacement(...)) { 40 | if (dots_n(...)) { 41 | # Using expr_text() because it might be a namespaced symbol 42 | replacement <- expr_text(expr(...)) 43 | } else { 44 | replacement <- NULL 45 | } 46 | 47 | msg <- deprecated_function_msg(name, version, type, replacement) 48 | 49 | signal("deprecated", 50 | name = name, 51 | replacement = replacement, 52 | version = version, 53 | .msg = msg 54 | ) 55 | return(invisible(NULL)) 56 | } 57 | 58 | replacements <- dots_list(...) 59 | args <- names(replacements) 60 | 61 | for (i in seq_along(replacements)) { 62 | msg <- deprecated_argument_msg(name, args[[i]], version, type, replacements[[i]]) 63 | 64 | signal("deprecated_arg", 65 | name = name, 66 | argument = args[[i]], 67 | replacement = replacements[[i]], 68 | version = version, 69 | .msg = msg 70 | ) 71 | } 72 | 73 | invisible(NULL) 74 | } 75 | deprecation_level <- function(cycle, pkg_version) { 76 | due_levels <- map_lgl(cycle, function(ver) !is_null(ver) && ver <= pkg_version) 77 | 78 | if (!any(due_levels)) { 79 | return(0) 80 | } 81 | 82 | max(which(due_levels)) 83 | } 84 | maybe_promote_deprecation <- function(level) { 85 | if (level < 3 && is_true(peek_option("oldie_verbose_retirement"))) { 86 | level <- level + 1 87 | } 88 | 89 | level 90 | } 91 | 92 | deprecated_function_msg <- function(name, version, type, 93 | replacement = NULL) { 94 | stopifnot( 95 | is_string(name), 96 | is_null(replacement) || is_string(replacement) 97 | ) 98 | 99 | msg <- sprintf("`%s()` is %s as of version %s", name, type, version) 100 | if (!is_null(replacement)) { 101 | msg <- sprintf("%s, please use `%s()` instead", msg, replacement) 102 | } 103 | 104 | msg 105 | } 106 | deprecated_argument_msg <- function(name, argument, version, type, 107 | replacement = "") { 108 | stopifnot( 109 | is_string(name), 110 | is_string(argument), 111 | is_string(replacement) 112 | ) 113 | 114 | msg <- sprintf("Argument `%s` of function `%s()` is %s as of version %s", 115 | argument, 116 | name, 117 | type, 118 | version 119 | ) 120 | if (!identical(replacement, "")) { 121 | msg <- sprintf("%s\nPlease use `%s` instead", msg, replacement) 122 | } 123 | 124 | msg 125 | } 126 | 127 | #' Promote or demote retirement levels 128 | #' 129 | #' When retirement levels are promoted, soft-deprecated functions 130 | #' issue a warning and deprecated functions issue an error. There is 131 | #' no change for defunct functions. 132 | #' 133 | #' You can check whether deprecation levels are promoted by inspecting 134 | #' the global option `oldie_verbose_retirement`. 135 | #' 136 | #' @export 137 | promote_retirement <- function() { 138 | poke_options(oldie_verbose_retirement = TRUE) 139 | } 140 | #' @rdname promote_retirement 141 | #' @export 142 | demote_retirement <- function() { 143 | poke_options(oldie_verbose_retirement = FALSE) 144 | } 145 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | 2 | slide <- function(.x, .f, ...) { 3 | n <- length(.x) 4 | if (n <= 1) { 5 | abort("Can't slide on a vector that is not at least length 2") 6 | } 7 | 8 | out <- list_len(n - 1) 9 | for (i in seq_len(n - 1)) { 10 | out[[i]] <- .f(.x[[i]], .x[[i + 1]], ...) 11 | } 12 | 13 | out 14 | } 15 | slide_lgl <- function(.x, .f, ...) { 16 | flatten_lgl(slide(.x, .f, ...)) 17 | } 18 | 19 | is_language <- is_lang 20 | 21 | bad_symbols <- function(chr) { 22 | bad <- glue::backtick(chr) 23 | bad <- glue::collapse(bad, ", ", last = " and ") 24 | bad 25 | } 26 | pluralise <- function(n, singular, plural) { 27 | if (n == 1) { 28 | singular 29 | } else { 30 | plural 31 | } 32 | } 33 | pluralise_len <- function(x, singular, plural) { 34 | pluralise(length(x), singular, plural) 35 | } 36 | 37 | is_namespaced_symbol <- function(x) { 38 | if (typeof(x) != "language") { 39 | return(FALSE) 40 | } 41 | 42 | first <- node_car(x) 43 | arg <- node_cadr(node_cdr(x)) 44 | identical(first, quote(`::`)) && is_symbol(arg) 45 | } 46 | -------------------------------------------------------------------------------- /R/version.R: -------------------------------------------------------------------------------- 1 | 2 | ver <- function(x) { 3 | stopifnot(is_string(x)) 4 | as_version(as.numeric_version(x)) 5 | } 6 | new_version <- function(x) { 7 | stopifnot(is_integerish(x)) 8 | ver(paste(x, collapse = ".")) 9 | } 10 | as_version <- function(x) { 11 | if (inherits(x, "numeric_version")) { 12 | set_attrs(x, class = "version") 13 | } else { 14 | abort("Can't convert `x` to a version") 15 | } 16 | } 17 | 18 | is_version <- function(x, n_components = NULL, max_digits = NULL, minor = NULL) { 19 | if (!inherits(x, "version")) { 20 | return(FALSE) 21 | } 22 | 23 | components <- ver_components(x) 24 | 25 | if (!is_null(n_components) && length(components) != n_components) { 26 | return(FALSE) 27 | } 28 | 29 | if (!is_null(max_digits)) { 30 | large <- log10(components) >= max_digits 31 | if (any(large)) { 32 | return(FALSE) 33 | } 34 | } 35 | 36 | if (!is_null(minor)) { 37 | is_minor <- components[[length(components)]] != 0 38 | if (!identical(minor, is_minor)) { 39 | return(FALSE) 40 | } 41 | } 42 | 43 | TRUE 44 | } 45 | 46 | #' @export 47 | `[[.version` <- function(x, i) { 48 | ver_components(x)[[i]] 49 | } 50 | #' @export 51 | `[[<-.version` <- function(x, i, value) { 52 | components <- ver_components(x) 53 | components[[i]] <- value 54 | new_version(components) 55 | } 56 | #' @export 57 | length.version <- function(x) { 58 | length(ver_components(x)) 59 | } 60 | #' @export 61 | Ops.version <- function(e1, e2) { 62 | # Ops.numeric_version() assumes the length method is not implemented 63 | e1 <- as.numeric_version(e1) 64 | e2 <- as.numeric_version(e2) 65 | 66 | # For some reason NextMethod() throws an error 67 | eval_bare(lang(.Generic, e1, e2), base_env()) 68 | } 69 | 70 | #' @export 71 | print.version <- function(x, ...) { 72 | print(as.numeric_version(x)) 73 | } 74 | #' @export 75 | as.character.version <- function(x, ...) { 76 | as.character(as.numeric_version(x)) 77 | } 78 | 79 | 80 | ver_components <- function(ver) { 81 | flatten_int(ver) 82 | } 83 | 84 | ver_bump <- function(ver, component = c("patch", "minor", "major")) { 85 | stopifnot(is_version(ver)) 86 | 87 | components <- ver_components(ver) 88 | i <- ver_component_index(component) 89 | 90 | stopifnot(length(components) >= i) 91 | components[[i]] <- components[[i]] + 1L 92 | 93 | new_version(components) 94 | } 95 | ver_unbump <- function(ver, component = c("patch", "minor", "major")) { 96 | stopifnot(is_version(ver)) 97 | 98 | i <- ver_component_index(component) 99 | 100 | if (i > length(ver)) { 101 | abort("Version does not have that many components") 102 | } 103 | if (i < 0) { 104 | abort("Can't supply negative version component") 105 | } 106 | 107 | x <- ver[[i]] 108 | 109 | if (x == 0) { 110 | ver[[i]] <- 9 111 | ver_unbump(ver, i - 1) 112 | } else { 113 | ver[[i]] <- x - 1 114 | ver 115 | } 116 | } 117 | ver_component_index <- function(component) { 118 | if (is_scalar_integer(component)) { 119 | return(component) 120 | } 121 | 122 | component <- arg_match(component, c("patch", "minor", "major")) 123 | 124 | i <- switch(component, 125 | major = 1L, 126 | minor = 2L, 127 | patch = 3L 128 | ) 129 | } 130 | 131 | ver_trim <- function(ver, max_components = 3) { 132 | stopifnot(is_version(ver)) 133 | 134 | if (length(ver) <= max_components) { 135 | return(ver) 136 | } 137 | 138 | components <- ver_components(ver) 139 | 140 | i <- min(length(components), max_components) 141 | i <- max(i, max_components) 142 | 143 | components <- components[seq_len(i)] 144 | new_version(components) 145 | } 146 | 147 | 148 | pkg_ver <- function(pkg) { 149 | stopifnot(is_string(pkg)) 150 | as_version(utils::packageVersion(pkg)) 151 | } 152 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # oldie 2 | 3 | [![Build Status](https://travis-ci.org/r-lib/oldie.svg?branch=master)](https://travis-ci.org/r-lib/oldie) 4 | [![Coverage status](https://codecov.io/gh/r-lib/oldie/branch/master/graph/badge.svg)](https://codecov.io/github/r-lib/oldie?branch=master) 5 | [![Stability](https://img.shields.io/badge/stability-archived-red.svg)](https://github.com/joethorley/stability-badges#archived) 6 | 7 | 8 | **oldie has been archived.** It is replaced by the [lifecycle package](https://lifecycle.r-lib.org). 9 | 10 | 11 | ## Overview 12 | 13 | This is an experimental package for deprecation of functions and 14 | arguments. Deprecation is spread over several releases with three 15 | levels of deprecation: 16 | 17 | - **Soft-deprecated**: This is the first stage of deprecation. The 18 | function or argument continues to work normally without any 19 | warning. Soft-deprecated functions will generally not be documented, 20 | and should not be used in examples or package code. They are left 21 | around so that existing code continues to work, but new code should 22 | not use them. 23 | 24 | - **Deprecated**: The function or argument now issues a warning when 25 | used or supplied. Users should upgrade their code to use the 26 | suggested replacement, if any. 27 | 28 | - **Defunct**: The function or argument now issues an error when used 29 | or supplied. 30 | 31 | The deprecation levels can be promoted by calling 32 | `promote_retirement()`. The `testthat` and the `strict` packages 33 | will probably automatically promote the deprecation levels to make 34 | sure you are not using any soft-deprecated functions in your code. 35 | 36 | 37 | ## Retirement 38 | 39 | ### Functions 40 | 41 | Let's create an obsolete function to illustrate how to retire it: 42 | 43 | ```{r} 44 | old_fn <- function() "old" 45 | ``` 46 | 47 | `retire()` is a function operator that takes a function and modifies 48 | it so that it automatically calls `oldie::signal_retired()`. It takes 49 | as second argument a release cycle: 50 | 51 | ```{r} 52 | old_fn <- retire(old_fn, c("0.1.0", "0.3.0", "0.5.0")) 53 | ``` 54 | 55 | In this release cycle the function is soft-deprecated from version 56 | 0.1.0 until 0.3.0. During that period it signals a simple condition. 57 | Between 0.3.0 and 0.5.0 it is deprecated and signals a warning. After 58 | 0.5.0 it is defunct and issues an error. 59 | 60 | If you supply a single version, oldie completes it on the assumption 61 | that each minor version increases the deprecation level. The following 62 | expressions are thus equivalent: 63 | 64 | ```{r} 65 | retire(old_fn, "0.1.0") 66 | retire(old_fn, c("0.1.0", "0.2.0", "0.3.0")) 67 | ``` 68 | 69 | If there is a new function that you can suggest as replacement, supply 70 | its name as third argument: 71 | 72 | ```{r} 73 | retire(old_fn, "0.1.0", "new_fn") 74 | 75 | # If it lives in another package make it clear with a namespace: 76 | retire(old_fn, "0.1.0", "pkg::new_fn") 77 | ``` 78 | 79 | 80 | ### Arguments 81 | 82 | When `retire()` is supplied *named arguments* it deprecates the 83 | function arguments rather than the function itself: 84 | 85 | ```{r} 86 | fn <- function(..., old, new) NULL 87 | 88 | # This deprecates the argument `old` with no replacement 89 | retire(fn, "0.1.0", old =) 90 | 91 | # This deprecates the argument `old` with replacement `new` 92 | retire(fn, "0.1.0", old = new) 93 | ``` 94 | 95 | If a user supplies the retired argument, two things happen: 96 | 97 | - A deprecation signal is sent. The signal follows the same rule as 98 | for function deprecation. 99 | 100 | - The value supplied to the deprecated argument is reassigned to the 101 | replacement, if there is one. 102 | 103 | Note that the retired argument is automatically added to the function 104 | formals if it's not there. This makes the code cleaner because retired 105 | arguments don't need to clutter the function definition: 106 | 107 | ```{r} 108 | fn <- function(..., new) NULL 109 | retire(fn, "0.1.0", old =) 110 | ``` 111 | 112 | However this might not be appropriate if the deprecated argument 113 | originally appeared before `...`. In that case the argument order 114 | might not be backward compatible. 115 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 10% 9 | patch: 10 | default: 11 | target: auto 12 | threshold: 10% 13 | -------------------------------------------------------------------------------- /man/oldie-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/oldie.R 3 | \docType{package} 4 | \name{oldie-package} 5 | \alias{oldie} 6 | \alias{oldie-package} 7 | \title{oldie: Tools for Deprecating and Retiring Functions or Arguments} 8 | \description{ 9 | A toolbox for marking functions or arguments as 10 | deprecated with different levels of verbosity defined by a release 11 | cycle. 12 | } 13 | \seealso{ 14 | Useful links: 15 | \itemize{ 16 | \item \url{https://github.com/r-lib/oldie} 17 | \item Report bugs at \url{https://github.com/r-lib/oldie/issues} 18 | } 19 | 20 | } 21 | \author{ 22 | \strong{Maintainer}: Lionel Henry \email{lionel@rstudio.com} 23 | 24 | Other contributors: 25 | \itemize{ 26 | \item RStudio [copyright holder] 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /man/promote_retirement.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/signal-retired.R 3 | \name{promote_retirement} 4 | \alias{promote_retirement} 5 | \alias{demote_retirement} 6 | \title{Promote or demote retirement levels} 7 | \usage{ 8 | promote_retirement() 9 | 10 | demote_retirement() 11 | } 12 | \description{ 13 | When retirement levels are promoted, soft-deprecated functions 14 | issue a warning and deprecated functions issue an error. There is 15 | no change for defunct functions. 16 | } 17 | \details{ 18 | You can check whether deprecation levels are promoted by inspecting 19 | the global option \code{oldie_verbose_retirement}. 20 | } 21 | -------------------------------------------------------------------------------- /man/retire.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/retire.R, R/signal-retired.R 3 | \name{retire} 4 | \alias{retire} 5 | \alias{signal_retired} 6 | \title{Deprecate a function or argument} 7 | \usage{ 8 | retire(.fn, .cycle, ..., .msg = NULL) 9 | 10 | signal_retired(.fn, .cycle, ..., .msg = NULL) 11 | } 12 | \arguments{ 13 | \item{.fn}{The function to deprecate or whose arguments are to be 14 | deprecated. This should be supplied as a bare name.} 15 | 16 | \item{.cycle}{A character vector defining the deprecation cycle. 17 | See the relevant section.} 18 | 19 | \item{...}{Replacements, supplied as bare names. 20 | \itemize{ 21 | \item If no replacement is supplied, the function is deprecated with no 22 | replacement. 23 | \item If a single unnamed replacement is supplied, the function is 24 | deprecated with the replacement. If the replacement function 25 | lives in another package, indicate it with a namespace: 26 | \code{"pkg::replacement"}. 27 | \item If one or several named replacements are supplied, the function 28 | is not deprecated. Instead, the supplied arguments are. \code{old = new} means that the argument \code{old} is deprecated with replacement 29 | \code{new}. \code{old =} means that the argument \code{old} is deprecated 30 | without replacement. 31 | }} 32 | 33 | \item{.msg}{An alternative deprecation message.} 34 | } 35 | \description{ 36 | \code{retire()} marks a function or some of its arguments as 37 | obsolete. This enables automatic documentation by roxygen, signals 38 | a condition when a deprecated function is run or when a deprecated 39 | argument is supplied, and checks that the deprecation cycle 40 | conforms to tidyverse rules. 41 | 42 | The conditions are signalled with with \code{signal_retired()} which 43 | has the same interface as \code{retire()}. It should always be called 44 | directly within the deprecated function. Since it is added 45 | automatically by \code{retire()}, you should rarely have to call it 46 | yourself. 47 | } 48 | \section{Deprecation levels}{ 49 | 50 | 51 | There are three deprecation levels: 52 | \itemize{ 53 | \item \strong{Soft-deprecated}: This is the first stage of deprecation. The 54 | function or argument continues to work normally without any 55 | warning. Soft-deprecated functions will generally not be documented, and 56 | should not be used in examples or package code. They are left 57 | around so that existing code continues to work, but new code 58 | should not use them. 59 | 60 | To make soft-deprecated functions signal an error, see below. 61 | \item \strong{Deprecated}: The function or argument now issues a warning 62 | when used or supplied. Users should upgrade their code to use the 63 | suggested replacement, if any. 64 | \item \strong{Defunct}: The function or argument now issues an error when 65 | used or supplied. 66 | } 67 | 68 | These levels are defined by a deprecation cycle, see section 69 | below. You can promote the current deprecation level by setting the 70 | global option \code{oldie_verbose_retirement} to \code{TRUE}. 71 | Soft-deprecated functions then become deprecated, deprecated 72 | functions become defunct, and so on. This is useful to check 73 | whether you're relying on any soft-deprecated functions or 74 | arguments. 75 | } 76 | 77 | \section{Deprecation cycle}{ 78 | 79 | 80 | \code{.cycle} associates each deprecation stage to a release version of 81 | your package. It should be a character vectors of three versions. 82 | \itemize{ 83 | \item \code{c("0.1.0", "0.3.0", "1.0.0")}: Soft-deprecation at after the 84 | 0.1.0 release, deprecation after 0.3.0, and defunct after 1.0.0. 85 | \item \code{"0.1.0"}: This is equivalent to \code{c("0.1.0", "0.2.0", "0.3.0")}. 86 | When a single version is supplied, it is assumed that it marks 87 | the start of a deprecation cycle that is bumped at each minor 88 | version (middle number). 89 | \item \code{c("0.1.0", "", "")}: The function is soft-deprecated but is not 90 | planned to ever be deprecated or defunct. This is useful for 91 | discouraging users from using a function without forcing them to 92 | change their code. 93 | } 94 | } 95 | 96 | \examples{ 97 | # Let's create an obsolete function: 98 | old_fn <- function() "old" 99 | 100 | # You can deprecate it without any replacement: 101 | retire(old_fn, "0.1.0") 102 | 103 | # The cycle above specifies only one version. The cycle is 104 | # automatically filled and the above expression is thus equivalent to: 105 | retire(old_fn, c("0.1.0", "0.2.0", "0.3.0")) 106 | 107 | # If there is a new function replacing the old one, just supply its 108 | # bare name: 109 | retire(old_fn, "0.1.0", replacement_fn) 110 | 111 | 112 | # Deprecating an argument is very similar. They are supplied as 113 | # key-value pairs where the key is the deprecated argument and the 114 | # value, if supplied, is the replacement. This deprecates an 115 | # argument without replacement: 116 | fn <- function(..., old) NULL 117 | retire(fn, "0.1.0", old = ) 118 | 119 | # This deprecates with replacement. The deprecated argument is 120 | # automatically reassigned to the replacement: 121 | fn <- function(..., new, old) NULL 122 | retire(fn, "0.1.0", old = new) 123 | 124 | # The deprecated argument will be added to the formals if 125 | # needed. This way you can omit the deprecated arguments from the 126 | # function declaration: 127 | fn <- function(..., new) NULL 128 | retire(fn, "0.1.0", old = new) 129 | } 130 | -------------------------------------------------------------------------------- /oldie.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | Encoding: UTF-8 9 | 10 | AutoAppendNewline: Yes 11 | StripTrailingWhitespace: Yes 12 | 13 | BuildType: Package 14 | PackageUseDevtools: Yes 15 | PackageInstallArgs: --no-multiarch --with-keep.source 16 | PackageRoxygenize: rd,collate,namespace 17 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library("testthat") 2 | library("oldie") 3 | 4 | test_check("oldie") 5 | -------------------------------------------------------------------------------- /tests/testthat/helper-conditions.R: -------------------------------------------------------------------------------- 1 | 2 | expect_condition <- function(expr, 3 | class = NULL, 4 | regex = NULL, 5 | ..., 6 | info = NULL, 7 | label = NULL) { 8 | object <- tryCatch(expr, condition = identity) 9 | 10 | if (is_na(class)) { 11 | expect_false(inherits(object, "condition"), info = info, label = label) 12 | return(invisible(object)) 13 | } 14 | 15 | expect_is(object, "condition", info = info, label = label) 16 | 17 | if (!is_null(class)) { 18 | expect_is(object, class, info = info, label = label) 19 | } 20 | if (!is_null(regex)) { 21 | expect_match(object$message, regex, ..., info = info, label = label) 22 | } 23 | 24 | invisible(object) 25 | } 26 | -------------------------------------------------------------------------------- /tests/testthat/helper-versioning.R: -------------------------------------------------------------------------------- 1 | 2 | future_rlang_ver <- function() { 3 | version <- pkg_ver("rlang") 4 | 5 | version <- ver_trim(version, 3) 6 | version <- ver_bump(version, "minor") 7 | version[[3]] <- 0 8 | 9 | version 10 | } 11 | past_rlang_ver <- function() { 12 | version <- pkg_ver("rlang") 13 | 14 | version <- ver_trim(version, 3) 15 | if (version[[3]] == 0) { 16 | version <- unbump(version, "minor") 17 | } else { 18 | version[[3]] <- 0 19 | } 20 | 21 | version 22 | } 23 | -------------------------------------------------------------------------------- /tests/testthat/test-cycle.R: -------------------------------------------------------------------------------- 1 | context("cycle") 2 | 3 | test_that("components are checked", { 4 | expect_error(ver_check(ver("0.0.0.0"), n_components = 3), "must have 3 components, not 4") 5 | expect_error(ver_check(ver("0.100.0"), max_digits = 2), "more than 2 digits") 6 | expect_error(ver_check(ver("0.99.0"), max_digits = 2), NA) 7 | expect_error(ver_check(ver("0.0.1"), minor = FALSE), "can't be a minor update") 8 | expect_error(ver_check(ver("0.1.0"), minor = TRUE), "must be a minor update") 9 | expect_error(ver_check(ver("0.0.1"), minor = NULL), NA) 10 | }) 11 | 12 | test_that("cycle must have at least one version", { 13 | expect_error(new_cycle(""), "`cycle` can't be empty") 14 | expect_error(new_cycle(c("", "", ""), "`cycle` can't be empty")) 15 | }) 16 | 17 | test_that("cycles are completed", { 18 | expect_identical(new_cycle("0.1.0"), new_cycle(c("0.1.0", "0.2.0", "0.3.0"))) 19 | expect_identical(new_cycle(c("0.1.0", "0.2.0")), new_cycle(c("0.1.0", "0.2.0", "0.3.0"))) 20 | }) 21 | 22 | test_that("cycle must be a character vector with 1 to 3 components", { 23 | expect_error(new_cycle(c("0.0.0", "0.1.0", "0.2.0", "0.4.0")), "must have 1, 2, or 3 components") 24 | expect_error(new_cycle(chr()), "must have 1, 2, or 3 components") 25 | expect_error(new_cycle(1L), "can't parse version") 26 | }) 27 | 28 | test_that("cycle must be monotonic", { 29 | expect_error(new_cycle(c("0.1.0", "0.1.0")), "must be monotonically increasing") 30 | expect_error(new_cycle(c("0.2.0", "0.1.0")), "must be monotonically increasing") 31 | }) 32 | 33 | test_that("versions are trimmed", { 34 | ver <- ver("0.1.0") 35 | 36 | expect_identical(ver_trim(ver, 4), ver) 37 | expect_identical(ver_trim(ver, 3), ver) 38 | expect_identical(ver_trim(ver, 2), ver("0.1")) 39 | }) 40 | 41 | test_that("new_cycle_chr() handles empty versions", { 42 | cycle <- c("0.1.0", "", "") 43 | expect_identical(new_cycle_chr(cycle), cycle) 44 | }) 45 | -------------------------------------------------------------------------------- /tests/testthat/test-retire.R: -------------------------------------------------------------------------------- 1 | context("retire") 2 | 3 | # Function ----------------------------------------------------------- 4 | 5 | test_that("can't deprecate function multiple times", { 6 | foo <- function() "returned" 7 | foo <- retire(foo, "0.1.0") 8 | expect_error(retire(foo, "0.1.0"), "Function `foo` is already deprecated") 9 | }) 10 | 11 | test_that("replacement function must be supplied as symbol", { 12 | foo <- function() "returned" 13 | expect_error(retire(foo, "0.1.0", 2), "must be a symbol") 14 | }) 15 | 16 | 17 | # Arguments ---------------------------------------------------------- 18 | 19 | test_that("replaced arguments must be symbols", { 20 | foo <- function(foo) "returned" 21 | expect_error(retire(foo, "0.1.0", arg = foo()), "must be symbols") 22 | }) 23 | 24 | test_that("replaced arguments must be named", { 25 | foo <- function(foo) "returned" 26 | expect_error(retire(foo, "0.1.0", foo = bar, replaced), "must be named") 27 | }) 28 | 29 | test_that("replaced arguments must exist in function", { 30 | foo <- function(foo, bar) "returned" 31 | expect_error(retire(foo, "0.1.0", bar = other), "Can't find replacement") 32 | }) 33 | 34 | test_that("can't deprecate the same argument twice", { 35 | foo <- function(foo) "returned" 36 | foo <- retire(foo, "0.1.0", bar = foo) 37 | expect_error(retire(foo, "0.1.0", bar = foo), "`bar` has already been deprecated") 38 | 39 | foo <- retire(foo, "0.1.0", baz = foo) 40 | expect_error(retire(foo, "0.1.0", bar = foo, baz = foo), "`bar` and `baz` have already been deprecated") 41 | }) 42 | 43 | test_that("new arguments are reassigned if old is supplied", { 44 | foo <- function(new) list(new, old) 45 | foo <- set_env(foo, ns_env("rlang")) 46 | foo <- retire(foo, "99.9.0", old = new) 47 | expect_identical(foo(, 2), list(2, 2)) 48 | }) 49 | 50 | test_that("no reassignment when new argument has not been declared", { 51 | foo <- set_env(function() old, ns_env("rlang")) 52 | foo <- retire(foo, "99.9.0", old = ) 53 | expect_error(foo(), "is missing, with no default") 54 | expect_identical(foo("arg"), "arg") 55 | }) 56 | 57 | test_that("deprecated arguments are stored in attributes", { 58 | foo <- set_env(function(new1, new2, new3) NULL, ns_env("rlang")) 59 | foo <- retire(foo, "0.1.0", old1 = new1, old2 = ) 60 | foo <- retire(foo, "0.2.0", old3 = new3) 61 | 62 | depr_args <- retired_args(foo) 63 | expect_named(depr_args, c("old1", "old2", "old3")) 64 | 65 | arg1 <- depr_args[[1]] 66 | arg2 <- depr_args[[2]] 67 | arg3 <- depr_args[[3]] 68 | expect_identical(arg1, list(replacement = "new1", cycle = c("0.1.0", "0.2.0", "0.3.0"))) 69 | expect_identical(arg2, list(replacement = "", cycle = c("0.1.0", "0.2.0", "0.3.0"))) 70 | expect_identical(arg3, list(replacement = "new3", cycle = c("0.2.0", "0.3.0", "0.4.0"))) 71 | }) 72 | 73 | test_that("new arguments are added without defaults", { 74 | foo <- function(new) NULL 75 | fmls <- formals(retire(foo, "0.1.0", old = )) 76 | expect_identical(as.list(fmls), alist(new = , old = )) 77 | 78 | replaced_fmls <- formals(retire(foo, "0.1.0", old = new)) 79 | expect_identical(as.list(replaced_fmls), alist(new = , old = )) 80 | }) 81 | 82 | test_that("can add mark existing argument as deprecated", { 83 | foo <- function(new, old) "foo()" 84 | expect_error(regexp = NA, retire(foo, "0.1.0", old = )) 85 | }) 86 | -------------------------------------------------------------------------------- /tests/testthat/test-signal-retired.R: -------------------------------------------------------------------------------- 1 | context("signal") 2 | 3 | past_ver <- as.character(past_rlang_ver()) 4 | 5 | soft_deprecated_cycle <- c(past_ver, "", "") 6 | deprecated_cycle <- c("", past_ver, "") 7 | defunct_cycle <- c("", "", past_ver) 8 | 9 | if (is_true(peek_option("oldie_verbose_retirement"))) { 10 | abort("Verbose deprecation should be disabled") 11 | } 12 | 13 | 14 | # Functions ---------------------------------------------------------- 15 | 16 | rlang_fn <- set_env(function() "returned", ns_env("rlang")) 17 | 18 | test_that("correct deprecation message is built", { 19 | cycle <- c("1.0.0", "2.0.0", "3.0.0") 20 | expect_identical(deprecated_function_msg("foo", "1.0.0", "soft-deprecated", "bar"), "`foo()` is soft-deprecated as of version 1.0.0, please use `bar()` instead") 21 | expect_identical(deprecated_function_msg("foo", "2.0.0", "deprecated"), "`foo()` is deprecated as of version 2.0.0") 22 | }) 23 | 24 | test_that("deprecated function does not signal if not in deprecation cycle", { 25 | future_ver <- as.character(future_rlang_ver()) 26 | oldie <- retire(rlang_fn, c(future_ver, "", "")) 27 | 28 | expect_true(is_retired(oldie)) 29 | expect_condition(oldie(), NA) 30 | }) 31 | 32 | test_that("deprecated function signals soft-deprecation", { 33 | oldie <- retire(rlang_fn, soft_deprecated_cycle) 34 | msg <- sprintf("deprecated as of version %s", past_ver) 35 | expect_condition(oldie(), "deprecated", msg) 36 | }) 37 | 38 | test_that("deprecated function signals deprecation", { 39 | oldie <- retire(rlang_fn, deprecated_cycle) 40 | expect_warning(oldie(), sprintf("deprecated as of version %s", past_ver)) 41 | }) 42 | 43 | test_that("deprecated function signals itself defunct", { 44 | oldie <- retire(rlang_fn, defunct_cycle) 45 | expect_error(oldie(), "defunct as of version 0.1.0") 46 | }) 47 | 48 | test_that("deprecation stages are promoted", { 49 | scoped_options(oldie_verbose_retirement = TRUE) 50 | 51 | oldie <- retire(rlang_fn, deprecated_cycle) 52 | expect_error(oldie(), sprintf("defunct as of version %s", past_ver)) 53 | 54 | oldie <- retire(rlang_fn, "99.9.0", bar) 55 | expect_condition(oldie(), "deprecated", "soft-deprecated as of version `undefined`") 56 | }) 57 | 58 | test_that("deprecated function signals a replacement if supplied", { 59 | oldie <- retire(rlang_fn, defunct_cycle, bar) 60 | expect_error(oldie(), "please use `bar()` instead", fixed = TRUE) 61 | }) 62 | 63 | test_that("deprecated function fails if not scoped in a namespace", { 64 | oldie <- function() "return" 65 | oldie <- retire(oldie, "99.9.0") 66 | expect_error(oldie(), "must be scoped in a namespace") 67 | }) 68 | 69 | test_that("can supply namespaced replacement function", { 70 | oldie <- retire(rlang_fn, defunct_cycle, pkg::bar) 71 | expect_error(oldie(), "please use `pkg::bar()` instead", fixed = TRUE) 72 | }) 73 | 74 | 75 | # Arguments ----------------------------------------------------------- 76 | 77 | rlang_fn <- set_env(function(new) "returned", ns_env("rlang")) 78 | 79 | test_that("correct argument deprecation message is built", { 80 | cycle <- c("1.0.0", "2.0.0", "3.0.0") 81 | expect_identical( 82 | deprecated_argument_msg("foo", "arg", "1.0.0", "soft-deprecated", "bar"), 83 | unclass(glue( 84 | "Argument `arg` of function `foo()` is soft-deprecated as of version 1.0.0 85 | Please use `bar` instead" 86 | )) 87 | ) 88 | expect_identical( 89 | deprecated_argument_msg("foo", "arg", "2.0.0", "deprecated"), 90 | "Argument `arg` of function `foo()` is deprecated as of version 2.0.0" 91 | ) 92 | }) 93 | 94 | test_that("deprecated argument signals soft-deprecation", { 95 | oldie <- retire(rlang_fn, soft_deprecated_cycle, old = new) 96 | expect_condition(oldie(old = foo), "deprecated_arg", 97 | glue( 98 | "Argument `old` of function `rlang_fn()` is soft-deprecated as of version 0.1.0 99 | Please use `new` instead" 100 | ), 101 | fixed = TRUE 102 | ) 103 | }) 104 | 105 | test_that("deprecated argument signals deprecation", { 106 | oldie <- retire(rlang_fn, deprecated_cycle, old = ) 107 | expect_warning(oldie(old = foo), 108 | "Argument `old` of function `rlang_fn()` is deprecated", 109 | fixed = TRUE 110 | ) 111 | }) 112 | 113 | test_that("deprecated argument signals defunction", { 114 | oldie <- retire(rlang_fn, defunct_cycle, old = ) 115 | expect_error(oldie(old = foo), 116 | "Argument `old` of function `rlang_fn()` is defunct", 117 | fixed = TRUE 118 | ) 119 | }) 120 | 121 | test_that("deprecated argument signals are promoted", { 122 | scoped_options(oldie_verbose_retirement = TRUE) 123 | oldie <- retire(rlang_fn, deprecated_cycle, old = ) 124 | expect_error(oldie(old = foo), 125 | "Argument `old` of function `rlang_fn()` is defunct", 126 | fixed = TRUE 127 | ) 128 | }) 129 | -------------------------------------------------------------------------------- /tests/testthat/test-version.R: -------------------------------------------------------------------------------- 1 | context("version") 2 | 3 | test_that("versions are vectors", { 4 | ver <- ver("1.0.0") 5 | expect_length(ver, 3) 6 | expect_identical(ver[[3]], 0L) 7 | ver[[3]] <- 9L 8 | expect_identical(ver[[3]], 9L) 9 | }) 10 | --------------------------------------------------------------------------------