lifecycle/0000755000176200001440000000000014123367372012221 5ustar liggesuserslifecycle/NAMESPACE0000644000176200001440000000101514123356455013435 0ustar liggesusers# Generated by roxygen2: do not edit by hand S3method(conditionMessage,lifecycle_warning_deprecated) S3method(print,lifecycle_warning_deprecated) export(badge) export(deprecate_soft) export(deprecate_stop) export(deprecate_warn) export(deprecated) export(expect_defunct) export(expect_deprecated) export(is_present) export(last_lifecycle_warnings) export(lint_lifecycle) export(lint_tidyverse_lifecycle) export(pkg_lifecycle_statuses) export(signal_experimental) export(signal_stage) export(signal_superseded) import(rlang) lifecycle/LICENSE0000644000176200001440000000004513762403061013217 0ustar liggesusersYEAR: 2020 COPYRIGHT HOLDER: RStudio lifecycle/README.md0000644000176200001440000000267614123362306013503 0ustar liggesusers# lifecycle [![R-CMD-check](https://github.com/r-lib/lifecycle/workflows/R-CMD-check/badge.svg)](https://github.com/r-lib/lifecycle/actions) [![Codecov test coverage](https://codecov.io/gh/r-lib/lifecycle/branch/master/graph/badge.svg)](https://codecov.io/gh/r-lib/lifecycle?branch=master) [![CRAN status](https://www.r-pkg.org/badges/version/lifecycle)](https://CRAN.R-project.org/package=lifecycle) [![Lifecycle: maturing](https://img.shields.io/badge/lifecycle-maturing-blue.svg)](https://lifecycle.r-lib.org/articles/stages.html#maturing) lifecycle provides a set of tools and conventions to manage the life cycle of your exported functions. - For a general motivation and introduction to the key concepts, watch Hadley's rstudio::global(2021) keynote ["Maintaining the house that the tidyverse built"](https://www.rstudio.com/resources/rstudioglobal-2021/maintaining-the-house-the-tidyverse-built/). - Read `vignette("stages")` to learn what it means for a function to be experimental, stable, deprecated, or superseded. - Read `vignette("manage")` to learn how to manage lifecycle changes in functions that you use. - Read `vignette("communicate")` to learn how to communicate lifecycle changes in the functions you write. ## Installation ``` r # Install release version from CRAN install.packages("lifecycle") # Install development version from GitHub devtools::install_github("r-lib/lifecycle") ``` lifecycle/man/0000755000176200001440000000000014123362332012763 5ustar liggesuserslifecycle/man/expect_deprecated.Rd0000644000176200001440000000277313777032236016746 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/expect.R \name{expect_deprecated} \alias{expect_deprecated} \alias{expect_defunct} \title{Does expression produce lifecycle warnings or errors?} \usage{ expect_deprecated(expr, regexp = NULL, ...) expect_defunct(expr) } \arguments{ \item{expr}{Expression that should produce a lifecycle warning or error.} \item{regexp}{Optional regular expression matched against the expected warning message.} \item{...}{ Arguments passed on to \code{\link[testthat:expect_match]{expect_match}} \describe{ \item{\code{perl}}{logical. Should Perl-compatible regexps be used?} \item{\code{fixed}}{logical. If \code{TRUE}, \code{pattern} is a string to be matched as is. Overrides all conflicting arguments.} }} } \description{ These functions are equivalent to \code{\link[testthat:expect_error]{testthat::expect_warning()}} and \code{\link[testthat:expect_error]{testthat::expect_error()}} but check specifically for lifecycle warnings or errors. To test whether a deprecated feature still works without causing a deprecation warning, set the \code{lifecycle_verbosity} option to \code{"quiet"}.\preformatted{test_that("feature still works", \{ withr::local_options(lifecycle_verbosity = "quiet") expect_true(my_deprecated_function()) \}) } } \details{ \code{expect_deprecated()} sets the \link[=verbosity]{lifecycle_verbosity} option to \code{"warning"} to enforce deprecation warnings which are otherwise only shown once every 8 hours. } lifecycle/man/lifecycle-package.Rd0000644000176200001440000000160414012426416016604 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/lifecycle-package.R \docType{package} \name{lifecycle-package} \alias{lifecycle} \alias{lifecycle-package} \title{lifecycle: Manage the Life Cycle of your Package Functions} \description{ Manage the life cycle of your exported functions with shared conventions, documentation badges, and user-friendly deprecation warnings. } \seealso{ Useful links: \itemize{ \item \url{https://lifecycle.r-lib.org/} \item \url{https://github.com/r-lib/lifecycle} \item Report bugs at \url{https://github.com/r-lib/lifecycle/issues} } } \author{ \strong{Maintainer}: Lionel Henry \email{lionel@rstudio.com} Authors: \itemize{ \item Hadley Wickham \email{hadley@rstudio.com} (\href{https://orcid.org/0000-0003-4757-117X}{ORCID}) } Other contributors: \itemize{ \item RStudio [copyright holder] } } \keyword{internal} lifecycle/man/signal_stage.Rd0000644000176200001440000000300414012426337015712 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/signal.R \name{signal_stage} \alias{signal_stage} \title{Signal other experimental or superseded features} \usage{ signal_stage(stage, what, env = caller_env()) } \arguments{ \item{stage}{Life cycle stage, either "experimental" or "superseded".} \item{what}{String describing what feature the stage applies too, using the same syntax as \code{\link[=deprecate_warn]{deprecate_warn()}}.} \item{env}{Pair of environments that define where \verb{deprecate_*()} was called (used to determine the package name) and where the function called the deprecating function was called (used to determine if \code{deprecate_soft()} should message). These are only needed if you're calling \verb{deprecate_*()} from an internal helper, in which case you should forward \code{env = caller_env()} and \code{user_env = caller_env(2)}.} } \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} \code{signal_stage()} allows you to signal life cycle stages other than deprecation (for which you should use \code{\link[=deprecate_warn]{deprecate_warn()}} and friends). There is no behaviour associated with this signal, but in the future we will provide tools to log and report on usage of experimental and superseded functions. } \examples{ foofy <- function(x, y, z) { signal_stage("experimental", "foofy()") x + y / z } foofy(1, 2, 3) } lifecycle/man/lint_lifecycle.Rd0000644000176200001440000000341414123037657016252 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/lint.R \name{pkg_lifecycle_statuses} \alias{pkg_lifecycle_statuses} \alias{lint_lifecycle} \alias{lint_tidyverse_lifecycle} \title{Lint usages of functions that have a non-stable life cycle.} \usage{ pkg_lifecycle_statuses( package, which = c("superseded", "deprecated", "questioning", "defunct", "experimental", "soft-deprecated", "retired") ) lint_lifecycle( packages, path = ".", pattern = "[.][Rr](md)?", which = c("superseded", "deprecated", "questioning", "defunct", "experimental", "soft-deprecated", "retired") ) lint_tidyverse_lifecycle( path = ".", pattern = "[.][Rr](md)?", which = c("superseded", "deprecated", "questioning", "defunct", "experimental", "soft-deprecated", "retired") ) } \arguments{ \item{package}{The name of an installed package.} \item{which}{The lifecycle statuses to retrieve. Include \code{NA} if you want to include functions without a specified lifecycle status in the results.} \item{packages}{One or more installed packages to query for lifecycle statuses.} \item{path}{The directory path to the files you want to search.} \item{pattern}{Any files matching this pattern will be searched. The default searches any files ending in \code{.R} or \code{.Rmd}.} } \description{ \itemize{ \item \code{lint_lifecycle} dynamically queries the package documentation for packages in \code{packages} for lifecycle annotations and then searches the directory in \code{path} for usages of those functions. \item \code{lint_tidyverse_lifecycle} is a convenience function to call \code{lint_lifecycle} for all the packages in the tidyverse. \item \code{pkg_lifecycle_statuses} returns a data frame of functions with lifecycle annotations for an installed package. } } lifecycle/man/macros/0000755000176200001440000000000014024303263014245 5ustar liggesuserslifecycle/man/macros/lifecycle.Rd0000644000176200001440000000012313514370224016473 0ustar liggesusers \newcommand{\lifecycle}{\Sexpr[results=rd, stage=render]{lifecycle::badge("#1")}} lifecycle/man/verbosity.Rd0000644000176200001440000000327413617304372015315 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/verbosity.R \name{verbosity} \alias{verbosity} \title{Control the verbosity of deprecation signals} \description{ There are 3 levels of verbosity for deprecated functions: silence, warning, and error. Since the lifecycle package avoids disruptive warnings, the default level of verbosity depends on the lifecycle stage of the deprecated function, on the context of the caller (global environment or testthat unit tests cause more warnings), and whether the warning was already issued (see the help for \link[=deprecate_soft]{deprecation functions}). You can control the level of verbosity with the global option \code{lifecycle_verbosity}. It can be set to: \itemize{ \item \code{"default"} or \code{NULL} for the default non-disruptive settings. \item \code{"quiet"}, \code{"warning"} or \code{"error"} to force silence, warnings or errors for deprecated functions. } Note that functions calling \code{\link[=deprecate_stop]{deprecate_stop()}} invariably throw errors. } \examples{ if (rlang::is_installed("testthat")) { library(testthat) mytool <- function() { deprecate_soft("1.0.0", "mytool()") 10 * 10 } # Forcing the verbosity level is useful for unit testing. You can # force errors to test that the function is indeed deprecated: test_that("mytool is deprecated", { rlang::with_options(lifecycle_verbosity = "error", { expect_error(mytool(), class = "defunctError") }) }) # Or you can enforce silence to safely test that the function # still works: test_that("mytool still works", { rlang::with_options(lifecycle_verbosity = "quiet", { expect_equal(mytool(), 100) }) }) } } lifecycle/man/figures/0000755000176200001440000000000014024414165014431 5ustar liggesuserslifecycle/man/figures/lifecycle-defunct.svg0000644000176200001440000000170413514277600020545 0ustar liggesuserslifecyclelifecycledefunctdefunct lifecycle/man/figures/lifecycle-maturing.svg0000644000176200001440000000170613514277600020745 0ustar liggesuserslifecyclelifecyclematuringmaturing lifecycle/man/figures/lifecycle-archived.svg0000644000176200001440000000170713514277600020705 0ustar liggesusers lifecyclelifecyclearchivedarchived lifecycle/man/figures/lifecycle-soft-deprecated.svg0000644000176200001440000000172613514277600022172 0ustar liggesuserslifecyclelifecyclesoft-deprecatedsoft-deprecated lifecycle/man/figures/lifecycle-questioning.svg0000644000176200001440000000171413514277600021463 0ustar liggesuserslifecyclelifecyclequestioningquestioning lifecycle/man/figures/lifecycle-superseded.svg0000644000176200001440000000171313630210056021247 0ustar liggesusers lifecyclelifecyclesupersededsuperseded lifecycle/man/figures/lifecycle-stable.svg0000644000176200001440000000167413514277600020375 0ustar liggesuserslifecyclelifecyclestablestable lifecycle/man/figures/lifecycle-experimental.svg0000644000176200001440000000171613514277600021615 0ustar liggesuserslifecyclelifecycleexperimentalexperimental lifecycle/man/figures/lifecycle-deprecated.svg0000644000176200001440000000171213514277600021214 0ustar liggesuserslifecyclelifecycledeprecateddeprecated lifecycle/man/figures/lifecycle-retired.svg0000644000176200001440000000170513514277600020554 0ustar liggesusers lifecyclelifecycleretiredretired lifecycle/man/deprecated.Rd0000644000176200001440000000257013617311420015355 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/arg.R \name{deprecated} \alias{deprecated} \alias{is_present} \title{Mark an argument as deprecated} \usage{ deprecated() is_present(arg) } \arguments{ \item{arg}{A \code{deprecated()} function argument.} } \description{ Signal deprecated argument by using self-documenting sentinel \code{deprecated()} as default argument. Test whether the caller has supplied the argument with \code{is_present()}. } \section{Magical defaults}{ We recommend importing \code{lifecycle::deprecated()} in your namespace and use it without the namespace qualifier. In general, we \href{https://principles.tidyverse.org/def-magical.html}{advise against} such magical defaults, i.e. defaults that cannot be evaluated by the user. In the case of \code{deprecated()}, the trade-off is worth it because the meaning of this default is obvious and there is no reason for the user to call \code{deprecated()} themselves. } \examples{ foobar_adder <- function(foo, bar, baz = deprecated()) { # Check if user has supplied `baz` instead of `bar` if (lifecycle::is_present(baz)) { # Signal the deprecation to the user deprecate_warn("1.0.0", "foo::bar_adder(baz = )", "foo::bar_adder(bar = )") # Deal with the deprecated argument for compatibility bar <- baz } foo + bar } foobar_adder(1, 2) foobar_adder(1, baz = 2) } lifecycle/man/signal_experimental.Rd0000644000176200001440000000117614012426337017314 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/signal.R \name{signal_experimental} \alias{signal_experimental} \alias{signal_superseded} \title{Deprecated funtions for signalling experimental and lifecycle stages} \usage{ signal_experimental(when, what, env = caller_env()) signal_superseded(when, what, with = NULL, env = caller_env()) } \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Please use \code{\link[=signal_stage]{signal_stage()}} instead } \keyword{internal} lifecycle/man/last_lifecycle_warnings.Rd0000644000176200001440000000225614123356455020162 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/warning.R \name{last_lifecycle_warnings} \alias{last_lifecycle_warnings} \title{Display last deprecation warnings} \usage{ last_lifecycle_warnings() } \description{ \code{last_lifecycle_warnings()} returns a list of all warnings that occurred during the last top-level R command, along with a backtrace. Use \code{print(last_lifecycle_warnings(), simplify = level)} to control the verbosity of the backtrace. The \code{simplify} argument supports one of \code{"branch"} (the default), \code{"collapse"}, and \code{"none"} (in increasing order of verbosity). } \examples{ # These examples are not run because `last_lifecycle_warnings()` does not # work well within knitr and pkgdown \dontrun{ f <- function() invisible(g()) g <- function() list(h(), i()) h <- function() deprecate_warn("1.0.0", "this()") i <- function() deprecate_warn("1.0.0", "that()") f() # Print all the warnings that occurred during the last command: last_lifecycle_warnings() # By default, the backtraces are printed in their simplified form. # Use `simplify` to control the verbosity: print(last_lifecycle_warnings(), simplify = "none") } } lifecycle/man/badge.Rd0000644000176200001440000000437514030346463014331 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/badge.R \name{badge} \alias{badge} \title{Embed a lifecycle badge in documentation} \usage{ badge(stage) } \arguments{ \item{stage}{A lifecycle stage as a string. Must be one of \code{"experimental"}, \code{"stable"}, \code{"superseded"}, or \code{"deprecated"}.} } \value{ An \code{Rd} expression describing the lifecycle stage. } \description{ To include lifecycle badges in your documentation: \enumerate{ \item Call \code{usethis::use_lifecycle()} to copy the badge images into the \verb{man/} folder of your package. \item Call \code{lifecycle::badge()} inside R backticks to insert a lifecycle badge:\preformatted{#' `r lifecycle::badge("experimental")` #' `r lifecycle::badge("deprecated")` #' `r lifecycle::badge("superseded")` } If the deprecated feature is a function, a good place for this badge is at the top of the topic description. If it is an argument, you can put the badge in the argument description. } The badge is displayed as an image in the HTML version of the documentation and as text otherwise. \code{lifecycle::badge()} is run by roxygen at build time so you don't need to add lifecycle to \verb{Imports:} just to use the badges. However, it's still good practice to add to \verb{Suggests:} so that it will be available to package developers. } \section{Badges}{ \itemize{ \item \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} \code{lifecycle::badge("experimental")} \item \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} \code{lifecycle::badge("stable")} \item \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#superseded}{\figure{lifecycle-superseded.svg}{options: alt='[Superseded]'}}}{\strong{[Superseded]}} \code{lifecycle::badge("superseded")} \item \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} \code{lifecycle::badge("deprecated")} } The meaning of these stages is described in \code{vignette("stages")}. } lifecycle/man/deprecate_soft.Rd0000644000176200001440000000751014012426337016247 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/deprecated.R \name{deprecate_soft} \alias{deprecate_soft} \alias{deprecate_warn} \alias{deprecate_stop} \title{Deprecate functions and arguments} \usage{ deprecate_soft( when, what, with = NULL, details = NULL, id = NULL, env = caller_env(), user_env = caller_env(2) ) deprecate_warn( when, what, with = NULL, details = NULL, id = NULL, env = caller_env() ) deprecate_stop(when, what, with = NULL, details = NULL, env = caller_env()) } \arguments{ \item{when}{A string giving the version when the behaviour was deprecated.} \item{what}{A string describing what is deprecated: \itemize{ \item Deprecate a whole function with \code{"foo()"}. \item Deprecate an argument with \code{"foo(arg)"}. \item Partially deprecate an argument with \code{"foo(arg = 'must be a scalar integer')"}. } You can optionally supply the namespace: \code{"ns::foo()"}, but this is usually not needed as it will be inferred from the caller environment.} \item{with}{An optional string giving a recommended replacement for the deprecated behaviour. This takes the same form as \code{what}.} \item{details}{In most cases the deprecation message can be automatically generated from \code{with}. When it can't, use \code{details} to provide a hand-written message. \code{details} can either be a single string or a character vector, which will be converted to a bulleted list.} \item{id}{The id of the deprecation. A warning is issued only once for each \code{id}. Defaults to the generated message, but you should give a unique ID when the message in \code{details} is built programmatically and depends on inputs, or when you'd like to deprecate multiple functions but warn only once for all of them.} \item{env, user_env}{Pair of environments that define where \verb{deprecate_*()} was called (used to determine the package name) and where the function called the deprecating function was called (used to determine if \code{deprecate_soft()} should message). These are only needed if you're calling \verb{deprecate_*()} from an internal helper, in which case you should forward \code{env = caller_env()} and \code{user_env = caller_env(2)}.} } \value{ \code{NULL}, invisibly. } \description{ These functions provide three levels of verbosity for deprecated functions. Learn how to use them in \code{vignette("communicate")}. \itemize{ \item \code{deprecate_soft()} warns only if the deprecated function is called from the global environment or from the package currently being tested. \item \code{deprecate_warn()} warns unconditionally. \item \code{deprecate_stop()} fails unconditionally. } Warnings are only issued once every 8 hours to avoid overwhelming the user. Control with \code{\link[=verbosity]{options(lifecycle_verbosity)}}. } \section{Conditions}{ \itemize{ \item Deprecation warnings have class \code{lifecycle_warning_deprecated}. \item Deprecation errors have class \code{lifecycle_error_deprecated}. } } \examples{ # A deprecated function `foo`: deprecate_warn("1.0.0", "foo()") # A deprecated argument `arg`: deprecate_warn("1.0.0", "foo(arg)") # A partially deprecated argument `arg`: deprecate_warn("1.0.0", "foo(arg = 'must be a scalar integer')") # A deprecated function with a function replacement: deprecate_warn("1.0.0", "foo()", "bar()") # A deprecated function with a function replacement from a # different package: deprecate_warn("1.0.0", "foo()", "otherpackage::bar()") # A deprecated function with custom message: deprecate_warn( when = "1.0.0", what = "foo()", details = "Please use `otherpackage::bar(foo = TRUE)` instead" ) # A deprecated function with custom bulleted list: deprecate_warn( when = "1.0.0", what = "foo()", details = c( x = "This is dangerous", i = "Did you mean `safe_foo()` instead?" ) ) } \seealso{ \code{\link[=lifecycle]{lifecycle()}} } lifecycle/DESCRIPTION0000644000176200001440000000247014123367372013732 0ustar liggesusersPackage: lifecycle Title: Manage the Life Cycle of your Package Functions Version: 1.0.1 Authors@R: c(person(given = "Lionel", family = "Henry", role = c("aut", "cre"), email = "lionel@rstudio.com"), person(given = "Hadley", family = "Wickham", role = "aut", email = "hadley@rstudio.com", comment = c(ORCID = "0000-0003-4757-117X")), person(given = "RStudio", role = "cph")) Description: Manage the life cycle of your exported functions with shared conventions, documentation badges, and user-friendly deprecation warnings. License: MIT + file LICENSE URL: https://lifecycle.r-lib.org/, https://github.com/r-lib/lifecycle BugReports: https://github.com/r-lib/lifecycle/issues Depends: R (>= 3.3) Imports: glue, rlang (>= 0.4.10) Suggests: covr, crayon, lintr, tidyverse, knitr, rmarkdown, testthat (>= 3.0.1), tools, tibble, vctrs VignetteBuilder: knitr Config/testthat/edition: 3 Encoding: UTF-8 RoxygenNote: 7.1.2 NeedsCompilation: no Packaged: 2021-09-24 14:46:50 UTC; lionel Author: Lionel Henry [aut, cre], Hadley Wickham [aut] (), RStudio [cph] Maintainer: Lionel Henry Repository: CRAN Date/Publication: 2021-09-24 15:30:02 UTC lifecycle/build/0000755000176200001440000000000014123362332013307 5ustar liggesuserslifecycle/build/vignette.rds0000644000176200001440000000043214123362332015645 0ustar liggesusersRN0tAGPHqj9U6:UޙxfGN,Bn ,+@2[[#9|ҹf}F@--W=3 ζ [ýơe,qY1]uEtY/8{8)T첎2$7hB.Wg3 \ $Taw'nw& ^>`qUce(7;⊹Q yV  message: `trace()` was deprecated in lifecycle 1.0.0. Backtrace: 1. testthat::expect_warning(f(), class = "lifecycle_warning_deprecated") 7. lifecycle:::f() 8. lifecycle:::g() 9. lifecycle:::h() full ==== message: `trace()` was deprecated in lifecycle 1.0.0. Backtrace: x 1. +-testthat::expect_warning(f(), class = "lifecycle_warning_deprecated") 2. | \-testthat:::expect_condition_matching(...) 3. | \-testthat:::quasi_capture(...) 4. | +-testthat:::.capture(...) 5. | | \-base::withCallingHandlers(...) 6. | \-rlang::eval_bare(quo_get_expr(.quo), quo_get_env(.quo)) 7. \-lifecycle:::f() 8. \-lifecycle:::g() 9. \-lifecycle:::h() lifecycle/tests/testthat/output/test-signal-message.txt0000644000176200001440000000172713774646614023221 0ustar liggesusers Inferred package name (here it is base b/c of testthat's eval env) ================================================================== `foo()` was deprecated in base 1.0.0 and is now defunct. Overridden package name ======================= `foo()` was deprecated in mypkg 1.0.0 and is now defunct. Replacement function ==================== `foo()` was deprecated in base 1.0.0 and is now defunct. Please use `bar()` instead. Replacement function with overridden package names (1) ====================================================== `quux()` was deprecated in foo 1.0.0 and is now defunct. Please use `foofy()` instead. Replacement function with overridden package names (2) ====================================================== `quux()` was deprecated in foo 1.0.0 and is now defunct. Please use `bar::foofy()` instead. Details ======= `foo()` was deprecated in base 1.0.0 and is now defunct. Please use `bar()` instead. # Before: foo() # After: bar() lifecycle/tests/testthat/output/test-signal-message-args.txt0000644000176200001440000000240413774646614024144 0ustar liggesusers Deprecated argument =================== The `quux` argument of `foo()` was deprecated in base 1.0.0 and is now defunct. Deprecated argument with function replacement ============================================= The `quux` argument of `foo()` was deprecated in base 1.0.0 and is now defunct. Please use `bar()` instead. Deprecated argument with argument replacement (same function) ============================================================= The `quux` argument of `foo()` was deprecated in base 1.0.0 and is now defunct. Please use the `foofy` argument instead. Deprecated argument with argument replacement (different function) ================================================================== The `quux` argument of `foo()` was deprecated in base 1.0.0 and is now defunct. Please use the `foofy` argument of `bar()` instead. Deprecated argument with argument replacement (different function, different package) ===================================================================================== The `quux` argument of `foo()` was deprecated in aaa 1.0.0 and is now defunct. Please use the `foofy` argument of `zzz::bar()` instead. Deprecated argument with reason =============================== The `quux` argument of `foo()` can't be a baz as of aaa 1.0.0. lifecycle/tests/testthat/test-badge.R0000644000176200001440000000030313777262126017366 0ustar liggesuserstest_that("badge doesn't change unexpected", { expect_snapshot(cat(badge("deprecated"))) expect_snapshot(cat(badge("experimental"))) expect_snapshot(cat(badge("unknown")), error = TRUE) }) lifecycle/tests/testthat/test-signal.R0000644000176200001440000000111314013165577017575 0ustar liggesuserstest_that("signal stage captures desired data", { f <- function() { signal_stage("experimental", "pkg::foo(bar = 'baz')") } cnd <- expect_condition(f(), class = "lifecycle_stage") expect_equal(cnd$stage, "experimental") expect_equal(cnd$package, "pkg") expect_equal(cnd$'function', "foo") expect_equal(cnd$argument, "bar") expect_equal(cnd$reason, "baz") }) test_that("signal generates user friendly message", { expect_snapshot({ (expect_condition(signal_stage("experimental", "foo()"))) (expect_condition(signal_stage("superseded", "foo(bar)"))) }) }) lifecycle/tests/testthat/test-spec.R0000644000176200001440000000257613777262126017274 0ustar liggesuserstest_that("spec() builds feature data", { expect_identical( spec("foo()"), spec_data(fn = "foo") ) expect_identical( spec("pkg::foo()"), spec_data(fn = "foo", pkg = "pkg") ) expect_identical( spec("foo(bar)"), spec_data(fn = "foo", arg = "bar") ) expect_identical( spec("foo(bar = )"), spec_data(fn = "foo", arg = "bar") ) expect_identical( spec("pkg::foo(bar = )"), spec_data(fn = "foo", arg = "bar", pkg = "pkg") ) expect_identical( spec("foo(bar = 'baz')"), spec_data(fn = "foo", arg = "bar", reason = "baz") ) expect_identical( spec("pkg::foo(bar = 'baz')"), spec_data(fn = "foo", arg = "bar", pkg = "pkg", reason = "baz") ) }) test_that("spec() gives useful errors", { expect_snapshot(spec(1), error = TRUE) expect_snapshot(spec("foo"), error = TRUE) expect_snapshot(spec("foo()()"), error = TRUE) expect_snapshot(spec("foo(arg = , arg = )"), error = TRUE) expect_snapshot(spec("foo(arg = arg)"), error = TRUE) e <- new_environment() local_options(topLevelEnvironment = e) expect_snapshot(spec("foo()", env = e), error = TRUE) }) test_that("spec() works with methods", { expect_identical( spec("A$foo()"), spec_data(fn = "A$foo") ) expect_identical( spec("A$foo(bar = )"), spec_data(fn = "A$foo", arg = "bar") ) expect_snapshot(spec("A$foo(bar = 1)"), error = TRUE) }) lifecycle/tests/testthat/test-arg.R0000644000176200001440000000035414040513302017056 0ustar liggesuserstest_that("deprecated() returns the missing argument", { fn <- function(foo = deprecated()) is_present(foo) expect_false(fn()) expect_true(fn(1)) fn <- function(foo) is_present(foo) expect_false(fn()) expect_true(fn(1)) }) lifecycle/tests/testthat/_snaps/0000755000176200001440000000000014123356455016506 5ustar liggesuserslifecycle/tests/testthat/_snaps/spec.md0000644000176200001440000000320014123357352017752 0ustar liggesusers# spec() gives useful errors Code spec(1) Error Internal error in lifecycle: `what` must be a string. --- Code spec("foo") Error Internal error in lifecycle: `what` must have function call syntax. # Good: signal_lifecycle("foo()") # Bad: signal_lifecycle("foo") --- Code spec("foo()()") Error Internal error in lifecycle: `what` must be a function or method call. --- Code spec("foo(arg = , arg = )") Error Internal error in lifecycle: Function in `what` (foo) must have 1 argument, not 2. --- Code spec("foo(arg = arg)") Error Internal error in lifecycle: `what` must contain reason as a string on the RHS of `=`. # Good: signal_lifecycle("foo(arg = 'must be a string')") # Bad: signal_lifecycle("foo(arg = 42)") --- Code spec("foo()", env = e) Error Internal error in lifecycle: Can't detect the package of the deprecated function. Please mention the namespace: # Good: signal_lifecycle(what = "namespace::myfunction()") # Bad: signal_lifecycle(what = "myfunction()") # spec() works with methods Code spec("A$foo(bar = 1)") Error Internal error in lifecycle: `what` must contain reason as a string on the RHS of `=`. # Good: signal_lifecycle("A$foo(arg = 'must be a string')") # Bad: signal_lifecycle("A$foo(arg = 42)") lifecycle/tests/testthat/_snaps/warning.md0000644000176200001440000000052314123357352020472 0ustar liggesusers# deprecation warning is displayed with backtrace Code last_lifecycle_warnings() Output [[1]] message: `trace()` was deprecated in lifecycle 1.0.0. Backtrace: 1. lifecycle::expect_deprecated(f()) 8. lifecycle f() 9. lifecycle g() 10. lifecycle h() lifecycle/tests/testthat/_snaps/deprecated.md0000644000176200001440000000524614123357351021133 0ustar liggesusers# what deprecation messages are readable Code cat_line(lifecycle_message("1.0.0", "foo()")) Output `foo()` was deprecated in base 1.0.0. Code cat_line(lifecycle_message("1.0.0", "foo()", signaller = "deprecate_stop")) Output `foo()` was deprecated in base 1.0.0 and is now defunct. Code cat_line(lifecycle_message("1.0.0", "foo(arg)")) Output The `arg` argument of `foo()` is deprecated as of base 1.0.0. Code cat_line(lifecycle_message("1.0.0", "foo(arg)", signaller = "deprecate_stop")) Output The `arg` argument of `foo()` was deprecated in base 1.0.0 and is now defunct. # replace deprecation messages are readable Code cat_line(lifecycle_message("1.0.0", "foo()", "package::bar()")) Output `foo()` was deprecated in base 1.0.0. Please use `package::bar()` instead. Code cat_line(lifecycle_message("1.0.0", "foo()", "bar()")) Output `foo()` was deprecated in base 1.0.0. Please use `bar()` instead. Code cat_line(lifecycle_message("1.0.0", "foo(arg1)", "foo(arg2)")) Output The `arg1` argument of `foo()` is deprecated as of base 1.0.0. Please use the `arg2` argument instead. Code cat_line(lifecycle_message("1.0.0", "foo(arg)", "bar(arg)")) Output The `arg` argument of `foo()` is deprecated as of base 1.0.0. Please use the `arg` argument of `bar()` instead. # unusual names are handled gracefully Code cat_line(lifecycle_message("1.0.0", "`foo-fy`(`qu-ux` = )")) Output The `qu-ux` argument of `foo-fy` is deprecated as of base 1.0.0. Code cat_line(lifecycle_message("1.0.0", "`foo<-`()")) Output `foo<-` was deprecated in base 1.0.0. Code cat_line(lifecycle_message("1.0.0", "`+`()")) Output `+` was deprecated in base 1.0.0. # can use bullets in details Code cat_line(lifecycle_message("1.0.0", "foo()", details = c("Unnamed", i = "Informative", x = "Error"), signaller = "deprecate_stop")) Output `foo()` was deprecated in base 1.0.0 and is now defunct. * Unnamed i Informative x Error # checks input types Code lifecycle_message(1) Error Internal error in lifecycle: `when` must be a string --- Code lifecycle_message("1", details = 1) Error Internal error in lifecycle: `details` must be a character vector # needs_warning works as expected Code needs_warning(1) Error Internal error in lifecycle: `id` must be a string --- Internal error in lifecycle: Expected `POSIXct` value in `needs_warning()`. lifecycle/tests/testthat/_snaps/badge.new.md0000644000176200001440000000124614123357350020660 0ustar liggesusers# badge doesn't change unexpected Code cat(badge("deprecated")) Output \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} --- Code cat(badge("experimental")) Output \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} --- Code cat(badge("unknown")) Error `stage` must be one of "experimental", "stable", "superseded", or "deprecated", not "unknown". lifecycle/tests/testthat/_snaps/signal.md0000644000176200001440000000046314123357352020305 0ustar liggesusers# signal generates user friendly message Code (expect_condition(signal_stage("experimental", "foo()"))) Output Code (expect_condition(signal_stage("superseded", "foo(bar)"))) Output lifecycle/tests/testthat/_snaps/deprecated.new.md0000644000176200001440000000524414123357351021721 0ustar liggesusers# what deprecation messages are readable Code cat_line(lifecycle_message("1.0.0", "foo()")) Output `foo()` was deprecated in base 1.0.0. Code cat_line(lifecycle_message("1.0.0", "foo()", signaller = "deprecate_stop")) Output `foo()` was deprecated in base 1.0.0 and is now defunct. Code cat_line(lifecycle_message("1.0.0", "foo(arg)")) Output The `arg` argument of `foo()` is deprecated as of base 1.0.0. Code cat_line(lifecycle_message("1.0.0", "foo(arg)", signaller = "deprecate_stop")) Output The `arg` argument of `foo()` was deprecated in base 1.0.0 and is now defunct. # replace deprecation messages are readable Code cat_line(lifecycle_message("1.0.0", "foo()", "package::bar()")) Output `foo()` was deprecated in base 1.0.0. Please use `package::bar()` instead. Code cat_line(lifecycle_message("1.0.0", "foo()", "bar()")) Output `foo()` was deprecated in base 1.0.0. Please use `bar()` instead. Code cat_line(lifecycle_message("1.0.0", "foo(arg1)", "foo(arg2)")) Output The `arg1` argument of `foo()` is deprecated as of base 1.0.0. Please use the `arg2` argument instead. Code cat_line(lifecycle_message("1.0.0", "foo(arg)", "bar(arg)")) Output The `arg` argument of `foo()` is deprecated as of base 1.0.0. Please use the `arg` argument of `bar()` instead. # unusual names are handled gracefully Code cat_line(lifecycle_message("1.0.0", "`foo-fy`(`qu-ux` = )")) Output The `qu-ux` argument of `foo-fy` is deprecated as of base 1.0.0. Code cat_line(lifecycle_message("1.0.0", "`foo<-`()")) Output `foo<-` was deprecated in base 1.0.0. Code cat_line(lifecycle_message("1.0.0", "`+`()")) Output `+` was deprecated in base 1.0.0. # can use bullets in details Code cat_line(lifecycle_message("1.0.0", "foo()", details = c("Unnamed", i = "Informative", x = "Error"), signaller = "deprecate_stop")) Output `foo()` was deprecated in base 1.0.0 and is now defunct. Unnamed i Informative x Error # checks input types Code lifecycle_message(1) Error Internal error in lifecycle: `when` must be a string --- Code lifecycle_message("1", details = 1) Error Internal error in lifecycle: `details` must be a character vector # needs_warning works as expected Code needs_warning(1) Error Internal error in lifecycle: `id` must be a string --- Internal error in lifecycle: Expected `POSIXct` value in `needs_warning()`. lifecycle/tests/testthat/_snaps/badge.md0000644000176200001440000000122114123357350020061 0ustar liggesusers# badge doesn't change unexpected Code cat(badge("deprecated")) Output \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} --- Code cat(badge("experimental")) Output \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} --- Code cat(badge("unknown")) Error `stage` must be one of "experimental", "stable", "superseded", or "deprecated". lifecycle/tests/testthat/test-lifecycle.R0000644000176200001440000000570414012426337020262 0ustar liggesuserstest_that("deprecate_soft() warns when called from global env", { local_options(lifecycle_verbosity = NULL) fn <- function(id) { deprecate_soft("1.0.0", "foo()", id = id) } expect_no_warning(fn("called from local env")) local_bindings(.env = global_env(), fn = fn) env_bind_lazy( current_env(), do = fn("called from global env"), .eval_env = global_env() ) expect_deprecated(do, "foo") }) test_that("deprecate_soft() warns when called from package being tested", { old <- Sys.getenv("NOT_CRAN") on.exit(Sys.setenv("NOT_CRAN" = old)) Sys.setenv("NOT_CRAN" = "true") retired <- function() deprecate_soft("1.0.0", "foo()") expect_warning(retired(), "was deprecated") }) test_that("deprecate_soft() warns when option is set", { retired <- function(id) deprecate_soft("1.0.0", "foo()", id = id) with_options(lifecycle_verbosity = "warning", { expect_warning(retired("rlang_test5"), "was deprecated") }) }) test_that("deprecate_warn() repeats warnings when option is set", { local_options(lifecycle_verbosity = "warning") retired1 <- function() deprecate_soft("1.0.0", "foo()", id = "signal repeat") retired2 <- function() deprecate_warn("1.0.0", "foo()", id = "warn repeat") expect_warning(retired1(), "was deprecated") expect_warning(retired2(), "was deprecated") expect_warning(retired1(), "was deprecated") expect_warning(retired2(), "was deprecated") }) test_that("can promote lifecycle warnings to errors", { local_options(lifecycle_verbosity = "error") expect_lifecycle_defunct(deprecate_soft("1.0.0", "foo()"), "was deprecated") expect_lifecycle_defunct(deprecate_warn("1.0.0", "foo()"), "was deprecated") }) test_that("soft-deprecation warnings are issued when called from child of global env as well", { fn <- function() deprecate_soft("1.0.0", "foo()", id = "child of global env") expect_warning(eval_bare(call2(fn), env(global_env())), "was deprecated") }) test_that("deprecation warnings are not displayed again", { local_options(lifecycle_verbosity = NULL) wrn <- catch_cnd( deprecate_warn("1.0.0", "foo()", id = "once-every-8-hours-note"), classes = "warning" ) footer <- wrn$internal$footer expect_true(is_string(footer) && grepl("once every 8 hours", footer)) local_options(lifecycle_verbosity = "warning") wrn <- catch_cnd(deprecate_warn("1.0.0", "foo()", id = "once-every-8-hours-no-note")) expect_false(grepl("once every 8 hours", wrn$message)) }) test_that("the topenv of the empty env is not the global env", { local_options(lifecycle_verbosity = NULL) expect_silent(deprecate_soft("1.0.0", "foo()", env = empty_env(), id = "topenv of empty env")) }) test_that("expect_deprecated() matches regexp", { expect_deprecated(deprecate_soft("1.0", "fn()", details = "foo"), "foo") expect_deprecated(deprecate_warn("1.0", "fn()", details = "foo.["), "foo.[", fixed = TRUE) expect_deprecated(expect_failure( expect_deprecated(deprecate_soft("1.0", "fn()"), "foo") )) }) lifecycle/tests/testthat/test-deprecated.R0000644000176200001440000001122714123355744020426 0ustar liggesusers# lifecycle verbosity ----------------------------------------------------- test_that("default deprecations behave as expected", { on.exit(env_unbind(deprecation_env, "test")) local_options(lifecycle_verbosity = "default") expect_condition(deprecate_soft("1.0.0", "foo()"), class = "lifecycle_soft_deprecated") expect_warning(deprecate_warn("1.0.0", "foo()", id = "test"), class = "lifecycle_warning_deprecated") expect_warning(deprecate_warn("1.0.0", "foo()", id = "test"), NA) expect_defunct(deprecate_stop("1.0.0", "foo()")) }) test_that("quiet suppresses _soft and _warn", { local_options(lifecycle_verbosity = "quiet") expect_warning(deprecate_soft("1.0.0", "foo()"), NA) expect_warning(deprecate_warn("1.0.0", "foo()"), NA) expect_defunct(deprecate_stop("1.0.0", "foo()")) }) test_that("warning always warns in _soft and _warn", { local_options(lifecycle_verbosity = "warning") expect_deprecated(deprecate_soft("1.0.0", "foo()")) expect_deprecated(deprecate_warn("1.0.0", "foo()")) expect_defunct(deprecate_stop("1.0.0", "foo()")) }) test_that("error coverts _soft and _warn to errors", { local_options(lifecycle_verbosity = "error") expect_defunct(deprecate_soft("1.0.0", "foo()")) expect_defunct(deprecate_warn("1.0.0", "foo()")) expect_defunct(deprecate_stop("1.0.0", "foo()")) }) test_that("soft deprecation uses correct calling envs", { local_options(lifecycle_verbosity = "default") # Simulate package functions available from global environment env <- new_environment(parent = ns_env("lifecycle")) local(envir = env, { softly <- function() { deprecate_soft("1.0.0", "softly()") } softly_softly <- function() { softly() } }) local_bindings(!!!as.list(env), .env = global_env()) # Calling package function directly should warning cnd <- catch_cnd(evalq(softly(), global_env()), "warning") expect_s3_class(cnd, class = "lifecycle_warning_deprecated") expect_match(conditionMessage(cnd), "lifecycle") # Calling package function indirectly from global env shouldn't cnd <- catch_cnd(evalq(softly_softly(), global_env()), "lifecycle_soft_deprecated") expect_s3_class(cnd, class = "lifecycle_soft_deprecated") expect_match(conditionMessage(cnd), "lifecycle") }) test_that("warning conditions are signaled only once if warnings are suppressed", { local_options(lifecycle_verbosity = "warning") x <- 0L suppressWarnings(withCallingHandlers( warning = function(...) x <<- x + 1L, deprecate_warn("1.0.0", "foo()") )) expect_identical(x, 1L) }) # messaging --------------------------------------------------------------- test_that("what deprecation messages are readable", { expect_snapshot({ cat_line(lifecycle_message("1.0.0", "foo()")) cat_line(lifecycle_message("1.0.0", "foo()", signaller = "deprecate_stop")) cat_line(lifecycle_message("1.0.0", "foo(arg)")) cat_line(lifecycle_message("1.0.0", "foo(arg)", signaller = "deprecate_stop")) }) }) test_that("replace deprecation messages are readable", { expect_snapshot({ cat_line(lifecycle_message("1.0.0", "foo()", "package::bar()")) cat_line(lifecycle_message("1.0.0", "foo()", "bar()")) cat_line(lifecycle_message("1.0.0", "foo(arg1)", "foo(arg2)")) cat_line(lifecycle_message("1.0.0", "foo(arg)", "bar(arg)")) }) }) test_that("unusual names are handled gracefully", { expect_snapshot({ cat_line(lifecycle_message("1.0.0", "`foo-fy`(`qu-ux` = )")) cat_line(lifecycle_message("1.0.0", "`foo<-`()")) cat_line(lifecycle_message("1.0.0", "`+`()")) }) }) test_that("can use bullets in details ", { expect_snapshot({ cat_line(lifecycle_message( "1.0.0", "foo()", details = c( "Unnamed", i = "Informative", x = "Error" ), signaller = "deprecate_stop" )) }) }) test_that("checks input types", { expect_snapshot(lifecycle_message(1), error = TRUE) expect_snapshot(lifecycle_message("1", details = 1), error = TRUE) }) # helpers ----------------------------------------------------------------- test_that("env_inherits_global works for simple cases", { expect_false(env_inherits_global(empty_env())) env <- new_environment(parent = global_env()) expect_true(env_inherits_global(env)) }) test_that("needs_warning works as expected", { on.exit(env_unbind(deprecation_env, "test")) expect_snapshot(needs_warning(1), error = TRUE) expect_true(needs_warning("test")) env_poke(deprecation_env, "test", Sys.time()) expect_false(needs_warning("test")) env_poke(deprecation_env, "test", Sys.time() - 9 * 60 * 60) expect_false(needs_warning("test")) env_poke(deprecation_env, "test", "x") expect_snapshot_error(needs_warning("test")) }) lifecycle/tests/testthat.R0000644000176200001440000000007613513316622015343 0ustar liggesuserslibrary(testthat) library(lifecycle) test_check("lifecycle") lifecycle/vignettes/0000755000176200001440000000000014123362332014220 5ustar liggesuserslifecycle/vignettes/stages.Rmd0000644000176200001440000001617214123356455016172 0ustar liggesusers--- title: "Lifecycle stages" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Lifecycle stages} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- This vignette describes the four primary stages of the tidyverse lifecycle: stable, deprecated, superseded, and experimental. ![A diagram showing the transitions between the four main stages: experimental can become stable and stable can become deprecated or superseded.](figures/lifecycle.svg){width="75%"} The lifecycle stages can apply to packages, functions, function arguments, and even specific values of a function argument. However, you'll mostly see them used to label individual functions, so that's the language we use below. ## Stable The default development stage is ![stable](figures/lifecycle-stable.svg){style="vertical-align:middle"}. A function is considered stable when the author is happy with its interface, doesn't see major issues, and is happy to share it with the world. Because this stage is the default, functions will only be given a stable badge if there's some specific need to draw attention to their status. Stability is defined in terms of breaking changes. A **breaking change** is a change that breaks code that uses the function as expected. In general, breaking changes reduce the set of code that works without error, either by removing a function, removing a function argument, or decreasing the set of valid inputs. Breaking changes also include changes to output type. If you imagine all the possible inputs to a function that return a result (and not an error), a breaking change makes the set smaller. Not all changes that cause your function to stop working are breaking changes. For example, you might have accidentally relied on a bug. When the bug is fixed, your code breaks, but this is not a breaking change. A good way of making your code more robust to this sort of behaviour change is to only use a function for its explicitly intended effects. For example, using `c()` to concatenate two vectors will not put your code at risk because it clearly is the intended usage of this function. On the other hand, using `c()` just for the side effect of removing attributes is probably not a good idea. If you use `c(factor("foo"))` to retrieve the underlying integers of the factor levels, your code is at risk of breaking if `c()` ever implements a factor method. Stable functions come with two promises related to breaking changes: - Breaking changes will be avoided where possible. We'll only make breaking changes if we consider the long term benefit of the change to be greater than short term pain of changing existing code. - If a breaking change is needed, it will occur gradually, through the deprecation process described next. This gives you plenty of time to adjust your code before it starts generating errors. ## Deprecated A ![Deprecated](figures/lifecycle-deprecated.svg){style="vertical-align:middle"} function has a better alternative available and is scheduled for removal. When you call a deprecated function, you get a warning telling you what to use instead. For example, take `tibble::as_data_frame()`: ```{r, eval = FALSE} df <- tibble::data_frame(x = 1) #> Warning message: #> `data_frame()` is deprecated as of tibble 1.1.0. #> Please use `tibble()` instead. #> This warning is displayed once every 8 hours. #> Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated. ``` The deprecation warning tells you when the function was deprecated (in tibble 1.1.0, released in 2016), and what to use instead (`tibble()`). To avoid being too annoying, deprecation messages will only appear once per session, and you can find out exactly where they come from by calling `lifecycle::last_lifecycle_warnings()`. `vignette("manage")` provides more advice on handling deprecation warnings in your code. Particularly important functions may go through two additional stages of deprecation: - **Soft deprecated** comes before deprecated. It's a gentler form of deprecation designed to prevent new uses of a function and encourage package developers to move away from it. Soft deprecated allows a package to change its interface to encourage package developers to update their code before their users are forced to change. - **Defunct** comes after deprecated. In most cases, a deprecated function will eventually just be deleted. For very important functions, we'll instead make the function defunct, which means that function continues to exist but the deprecation warning turns into an error. This is more user-friendly than just removing the function because users will get a clear error message explaining why their code no longer works and how they can fix it. ## Superseded A softer alternative to deprecation is superseded. A ![superseded](figures/lifecycle-superseded.svg)[^1] function has a known better alternative, but the function itself is not going away . A superseded function will not emit a warning (since there's no risk if you keep using it), but the documentation will tell you what we recommend instead . [^1]: This stage was previously called retired. Superseded functions will not receive new features, but will receive any critical bug fixes needed to keep it working. In some ways a superseded function is actually safer than a stable function because it's guaranteed never to change (for better or for worse). ## Experimental Some functions are released in an ![experimental](figures/lifecycle-experimental.svg){style="vertical-align:middle"} stage. Experimental functions are made available so people can try them out and provide feedback, but come with no promises for long term stability. In particular, the author reserves the right to make breaking changes without a deprecation cycle. That said, there is some interaction between popularity and stability. Breaking an popular function, even if clearly labelled as experimental, is likely to cause widespread pain so we'll generally try to avoid it. In general, you can assume any package with version number less than 1.0.0 is at least somewhat experimental, and it may have major changes in its future. The most experimental packages only exist on GitHub. If you're using a non-CRAN package you should plan for an active relationship: when the package changes, you need to be prepared to update your code. ## Superseded stages We no longer use these stages, but we document them here because we have used them in the past. ### Questioning Sometimes the author of a function is no longer certain that a function is the optimal approach, but doesn't yet know how to do it better. These functions can be marked as ![questioning](figures/lifecycle-questioning.svg){style="vertical-align:middle"} to give users a heads up that the author has doubts about the function. Because knowing that a function is questioning is not very actionable, we no longer use or recommend this stage. ### Maturing Previously we used as ![maturing](figures/lifecycle-maturing.svg){style="vertical-align:middle"} for functions that lay somewhere between experimental and stable. We stopped using this stage because, like questioning, it's not clear what actionable information this stage delivers. lifecycle/vignettes/figures/0000755000176200001440000000000014024443527015672 5ustar liggesuserslifecycle/vignettes/figures/lifecycle-defunct.svg0000644000176200001440000000170413515611214021774 0ustar liggesuserslifecyclelifecycledefunctdefunct lifecycle/vignettes/figures/lifecycle-maturing.svg0000644000176200001440000000170613515611214022174 0ustar liggesuserslifecyclelifecyclematuringmaturing lifecycle/vignettes/figures/lifecycle-archived.svg0000644000176200001440000000170713515611214022134 0ustar liggesusers lifecyclelifecyclearchivedarchived lifecycle/vignettes/figures/lifecycle-soft-deprecated.svg0000644000176200001440000000172613515611214023421 0ustar liggesuserslifecyclelifecyclesoft-deprecatedsoft-deprecated lifecycle/vignettes/figures/lifecycle-questioning.svg0000644000176200001440000000171413515611214022712 0ustar liggesuserslifecyclelifecyclequestioningquestioning lifecycle/vignettes/figures/lifecycle.svg0000644000176200001440000000360114012426337020347 0ustar liggesusers Canvas 1 Layer 1 experimental stable superseded deprecated lifecycle/vignettes/figures/lifecycle-superseded.svg0000644000176200001440000000171413762403061022513 0ustar liggesusers lifecyclelifecyclesupersededsuperseded lifecycle/vignettes/figures/lifecycle-stable.svg0000644000176200001440000000167413515611214021624 0ustar liggesuserslifecyclelifecyclestablestable lifecycle/vignettes/figures/lifecycle-experimental.svg0000644000176200001440000000171613515611214023044 0ustar liggesuserslifecyclelifecycleexperimentalexperimental lifecycle/vignettes/figures/example-badge.png0000644000176200001440000003056213515611214021073 0ustar liggesusersPNG  IHDR m4 iCCPICC ProfileHTtZBUJFHBJ AŮ,ZKW`aTAu],Pyy͝{̙9@3`U2E9 _FBb? @4PnhnXOqyRP$)\)'sĒo$G<-($h(wM02oD!@ >dgrh#ʶ"P2e/(̚S)l6_{fT^ T StਉzN U(en =M@;_ssæ8URaL1O=Œ(ETsْ麲X_c) b8W7wѡ1~ D' {/ks1牘9 ޸ 0g|@ $E Hl;@9p$8 .{@s0ށ1BZ>dYA+@aP%C|Hɠz*ʡP-3 .Ch^C`&4X6gî0c0Ά|x3\W‡&,|sxBG kC"$$ BD6ȑG C00L0&dcVa6a15&y fK`X6.`KUFlvqf8\0.[ۄۃkzqQ<{#l| ?@P" $PJ8D8MNxB#M"xFF"Hf$OR )TF'] 'QRR2TrS$TZTtTҀG:ْG^@7;7 ŔCIP6Sj)()6,ej &/U*&*LE*y**UP%UWVRU٩EemR;vY:^T=@~@ QzA G3hi"Z7mDC]Q#NcF) 9Y 1z? gϸ>LMMfff'-VV6fmKyKj_~16c&gfc3::Q:utuw}GK+;7O`02yƈA`Aa:F$#WTc}puwM&&&&MLM76>54cՙ7{gWߴYZ[豄-,׬`+g+YYnD*gݲ&[3sl6a6lm^64{_ml3l޳S [gfҞc_aӁڡᕣ#qm'S/.zacd.\i\/a|Vt~OktCO98gГSJKm~cy´`13_J|}kG bGcCb8ZHKʐGaap8<$|{&sEs#@+b{ăH_EΫ8.jETg45zqw11[bŚb;Tƽ/'NXp5Q;QؒOKJ0N /4[tEڋ2Zx269>Pgv=Jٝ29c:?arbw# jZ4,h$vy5bKI4Nm9M:zLޙvq;K8wB/\x\'%K'/_nzզ._~mvnrǭwNq&վ}o-%ͽNƝWws[s{҇:+A,?5?(ѽAߥLy\DIS'{6\|Ej~iğ>v$ ֛귎o;F#G|7և;?z23s/m_C%oRA j(v@51ifФF?fT0TE dtA1>vpP4~2UotE2>>g| ۑ;gOj á_4Gԥ5 #ɿeiTXtXML:com.adobe.xmp 269 109 d$IDATx]|TRHB !J 4?]"(iR(MDPADzfEҫH@ j%߆=^ w.x;?r}qnV MFFm,i4z"h4v!]p@#](R |dilߵ~ZUki]B7=Z6m !4$##qI=~F iDҕ ’_WRBB*W( hkШP4Ə7Ok~F~';Hw?:z,iO92}FP` &,{~,^,(YP A!bW"AL;*/++_<{4%G/ؔq5m7LYѹ;oޔ=}&RN +]rRD4~P܈G5SE,dϚU6%b4i۱`Wț;>9f~3iiI"eA ݺur fͱhk֨Z5{sDt:/p}T1ޭC;53jͼrX*fɊ:/ӛmQ晒ȟ7\?g>:ԭUa!U6pxɧpwG-_ a_Ͷ>@x ^AUju&rr!O7~/ZeQC~ڸC"((\\=HF21|vTfϝo*gFYjth#+\LysFݒ_6'NE;ȑ=3AXEQ"zy<_YhPɓ.`NGK7лGWY/k'ُ"Ӎ;a0)NaqtY@lgХ{,؁b89/]!P}$uW,Wc?"ǪQ>vLGU1R_-v6@-].hbe߶c)Mj#% ^#qXׄ`#_}{t.ɬڿ^qK6:P CdiܽkWdYz .w+ű{5:Foxݪ:bPqmnJAh*^H} Ò+My6sQ9ؿ[/5>w_[˱e}zˉ^׮_gQIr~Ytq _Fg w MMŲ\m!Qx"9)e {Y8< qqI'2~ χz8r,߁I}AdXfx@1EbT<[rqFJ<{u=7 >s>LF]U7&j$1%0qa Ѳ^)c(a Yhjz?aw~=9i2#~=Sj F^Z4cR)y1␞h#nϐ!1jqDLCFHҬ !He'LϴMē$xRa!m߽GQxqWeJin߾-D;wIuk0f-aJKl*|>D_ieb{ZP[q7{mץNs1TŤڃN6[siES,kh-RHČq*yr'٘{/*B2ʊ=VaN$YVz3@jլt$ Wh_KK?I &WU:!֑sθ+8!yi9xNxXw-R$qB{/^dțn=P5G!+=q \ 6Vz[X>+?nl)1K)ψ`5ڰtqgY5'FVmTNDKCFLF^_BaN3։Sf T1lYä͂1);cf=T7*W %4Z6m,LBK>MvdKhmZh7Z}zI{x2X/eS>.Rtcj<B8iOqM)58vYfCnF<2<Ƃ5)ah>}cRҝp^;cHE܎~5bޘmƢRr)Ku_ܓ6g#ufeMxD99hO#ڜ(lO:}ZޒwuqO+}q"ʑԴKY":K( {\4ir'F tr@~(ykN "WEPFr8niEI* y/ J[nEŗdՌȰc ;!y@{0]試_D!"M_ e,HY\E9ɕu1 '^a^ lb4!nc9}%qq3JnuPD8Px*YBFia f5&:Hf(b C{k`O.yHU'fioker<5RRR3) !#Byxl޻ (S$m,cT+,R~y/ƍ K濷I-%#P)l'17SMB〈 H~"ќh01\h G'0 +7Wiqb.X@ & 'ʤSwwI{2 l; Ӹ+ILe_Ѩe E g16835ҁH'"߷x(Cm7ǃǒvf7.aEMq#xP{C| 罱m[nCWb~>7[=LX63 ĕY|GpEb*eda|yp>:]JŔ6-Cmk,踔sD a$LSNJc^Z߫P{~q.:r.rQRKTN~Z l uNg{7nd!nS"n\nߔeS"#MyJaY0FqBq4Fס8?}VBF B A7A#JhJ۪ppAMZhhj-4`t44Z'@@ '+!+nF B A7A#JhJ۪ppAMZhhj-4`t44Z'@@ '+!+nF B A7A#JhJ۪ppAMZhhj-4`t44Z'@@ '+!+nF B A7A#JHqw#OFD b1٩?6Bh ȎhtH$џMF `yh\FW[+M-vh9W_&֨V&4:[_|9 QBc]G&uQ? +GԩYeJO#o\4KU) HSPAL3a!(\mӯ_r,꛹?l˙=;J?Sr:A#l<ЈQQ_]I4i$KW8p_pW#\)LvhЄ8vGf/ ¬z\25?{JMa}*9y?`{tPha7;n'3C̙lX#|p0hG #Q'ԅKM&L"EF)xdA3#48DvSH>=0aMn$r)@Q-4qZ/Ƥå?3pBm$ر{ò;mpbRCQ#Z{)8!4kzΗ',y$bI# #$'GGFIA.M#OCxzzcgJ'#Չa,ghX,mpA?7m*WB[;EǓO#Nc'Oi?o^4_B,dϒ`*>J)ͤ]:H+hyOu2iΉUfs[v̙3^znjEWJR◚2eʠR_gKk_,뫒*dB"OP}X}F~r8 V}ai+VϼOHH#Gзo_"_|3ď7{T<;?e5.8վ)ra\OɤWN746{O4΄Ug f͚aɒ%h߾=įSq9t9svk׮Ν;%5kD&Mׯ'zƍůƪU`^_~jT3CQ̿<7OQ?Wkg*a}rlY~E p,JC],7ZP.L87D9>֓+= Dn"8s~eԇy{UjBf>wV񳉇FCΝ{n&2i$ݻ D-ЧO;v М9pUիWKӇf1/w@1yCnx}vm!! u,-=# _^"/Xgf&W^s` UQI"[ Ǝ#ז79ӛ xD}pZ4U7t;:@KȒ%4M*W @5j$5ҥK֭ŋ"#+VN:?˗-7u2UT)xp"l4DV6>^,^J!SL8qh~ۇhiw\)h>МAťM>K{'O>P/+ϗݢ쫊_)vy?;;S^Tjw0vӇ8uoWjZ0ߔF Čג蓔OE Lf@={`/y]8I0Tp&i';/]$صk C4C2fhƒP,]mڴAPP4eX NU(u]Nb9gIhw:{5 ̈zAL.'6¿廯IP.TQi۪I#LX\\SVϧN1F߃#SG[Vo}lifecyclelifecycledeprecateddeprecated lifecycle/vignettes/figures/lifecycle-retired.svg0000644000176200001440000000170513515611214022003 0ustar liggesusers lifecyclelifecycleretiredretired lifecycle/vignettes/communicate.Rmd0000644000176200001440000002712714123354433017204 0ustar liggesusers--- title: "Communicate lifecycle changes in your functions" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Communicate lifecycle changes in your functions} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) options( # Pretend we're in the lifecycle package "lifecycle:::calling_package" = "lifecycle", # suppress last_lifecycle_warnings() message by default "lifecycle_verbosity" = "warning" ) ``` lifecycle provides a standard way to document the lifecycle stages of functions and arguments, paired with tools to steer users away from deprecated functions. Before we go in to the details, make sure that you're familiar with the lifecycle stages as described in `vignette("stages")`. ## Basics lifecycle badges make it easy for users to see the lifecycle stage when reading the documentation. To use the badges, first call `usethis::use_lifecycle()` to embed the badge images in your package (you only need to do this once), then use `lifecycle::badge()` to insert a badge: ```{r, eval = FALSE} #' `r lifecycle::badge("experimental")` #' `r lifecycle::badge("deprecated")` #' `r lifecycle::badge("superseded")` ``` Deprecated functions also need to advertise their status when run. lifecycle provides `deprecate_warn()` which takes three main arguments: - The first argument, `when`, gives the version number when the deprecation occurred. - The second argument, `what`, describes exactly what was deprecated. - The third argument, `with`, provides the recommended alternative. We'll cover the details shortly, but here are a few sample uses: ```{r} lifecycle::deprecate_warn("1.0.0", "old_fun()", "new_fun()") lifecycle::deprecate_warn("1.0.0", "fun()", "testthat::fun()") lifecycle::deprecate_warn("1.0.0", "fun(old_arg)", "fun(new_arg)") ``` (Note that the message includes the package name --- this is automatically discovered from the environment of the calling function so will not work unless the function is called from the package namespace.) The following sections describe how to use lifecycle badges and functions together to handle a variety of common development tasks. ## Functions ### Deprecate a function First, add a badge to the the `@description` block[^1]. Briefly describe why the deprecation occurred and what to use instead. [^1]: We only use an explicit `@description` when the description will be multiple paragraphs, as in these examples. ```{r} #' Add two numbers #' #' @description #' `r lifecycle::badge("deprecated")` #' #' This function was deprecated because we realised that it's #' a special case of the [sum()] function. ``` Next, update the examples to show how to convert from the old usage to the new usage: ```{r} #' @examples #' add_two(1, 2) #' # -> #' sum(1, 2) ``` Then add `@keywords internal` to remove the function from the documentation index. If you use pkgdown, also check that it's no longer listed in `_pkgdown.yml`. These changes reduce the chance of new users coming across a deprecated function, but don't prevent those who already know about it from referring to the docs. ```{r} #' @keywords internal ``` You're now done with the docs, and it's time to add a warning when the user calls your function. Do this by adding call to `deprecate_warn()` on the first line of the function: ```{r} add_two <- function(x, y) { lifecycle::deprecate_warn("1.0.0", "add_two()", "base::sum()") x + y } add_two(1, 2) ``` `deprecate_warn()` generates user friendly messages for two common deprecation alternatives: - Function in same package: `lifecycle::deprecate_warn("1.0.0", "fun_old()", "fun_new()")` - Function in another package: `lifecycle::deprecate_warn("1.0.0", "old()", "package::new()")` For other cases, use the `details` argument to provide your own message to the user: ```{r} add_two <- function(x, y) { lifecycle::deprecate_warn( "1.0.0", "add_two()", details = "This function is a special case of sum(); use it instead." ) x + y } add_two(1, 2) ``` It's good practice to test that you've correctly implemented the deprecation, testing that the deprecated function still works and that it generates a useful warning. Using an expectation inside `testthat::expect_snapshot()`[^2] is a convenient way to do this: [^2]: You can learn more about snapshot testing in `vignette("snapshotting", package = "testthat")`. ```{r, eval = FALSE} test_that("add_two is deprecated", { expect_snapshot({ x <- add_two(1, 1) expect_equal(x, 2) }) }) ``` If you have existing tests for the deprecated function you can suppress the warning in those tests with the `lifecycle_verbosity` option: ```{r, eval = FALSE } test_that("add_two returns the sum of its inputs", { withr::local_options(lifecycle_verbosity = "quiet") expect_equal(add_two(1, 1), 2) }) ``` And then add a separate test specifically for the deprecation. ```{r, eval = FALSE} test_that("add_two is deprecated", { expect_snapshot(add_two(1, 1)) }) ``` ### Gradual deprecation For particularly important functions, you can choose to add two other stages to the deprecation process: - `deprecate_soft()` is used before `deprecate_warn()`. This function only warns (a) users who try the feature from the global environment and (b) developers who directly use the feature (when running testthat tests). There is no warning when the deprecated feature is called indirectly by another package --- the goal is to ensure that warn only the person who has the power to stop using the deprecated feature. - `deprecate_stop()` comes after `deprecate_warn()` and generates an error instead of a warning. The main benefit over simply removing the function is that the user is informed about the replacement. If you use these stages you'll also need a process for bumping the deprecation stage for major and minor releases. We recommend something like this: 1. Search for `deprecate_stop()` and consider if you're ready to the remove the function completely. 2. Search for `deprecate_warn()` and replace with `deprecate_stop()`. Remove the remaining body of the function and any tests. 3. Search for `deprecate_soft()` and replace with `deprecate_warn()`. ### Rename a function To rename a function without breaking existing code, move the implementation to the new function, then call the new function from the old function, along with a deprecation message: ```{r} #' Add two numbers #' #' @description #' `r lifecycle::badge("deprecated")` #' #' `add_two()` was renamed to `number_add()` to create a more #' consistent API. #' @keywords internal #' @export add_two <- function(foo, bar) { lifecycle::deprecate_warn("1.0.0", "add_two()", "number_add()") number_add(foo, bar) } # documentation goes here... #' @export number_add <- function(x, y) { x + y } ``` If you are renaming many functions as part of an API overhaul, it'll often make sense to document all the changes in one file, like . ### Supersede a function Superseding a function is simpler than deprecating it, since you don't need to steer users away from it with a warning. So all you need to do is add a superseded badge: ```{r} #' Gather columns into key-value pairs #' #' @description #' `r lifecycle::badge("superseded")` ``` Then describe why the function was superseded, and what the recommended alternative is: ```{r} #' #' Development on `gather()` is complete, and for new code we recommend #' switching to `pivot_longer()`, which is easier to use, more featureful, #' and still under active development. #' #' In brief, #' `df %>% gather("key", "value", x, y, z)` is equivalent to #' `df %>% pivot_longer(c(x, y, z), names_to = "key", values_to = "value")`. #' See more details in `vignette("pivot")`. ``` The rest of the documentation can stay the same. If you're willing to live on the bleeding edge of lifecycle, add a call to the experimental `signal_stage()`: ```{r} gather <- function(data, key = "key", value = "value", ...) { lifecycle::signal_stage("superseded", "gather()") } ``` This signal isn't currently hooked up to any behaviour, but we plan to provide logging and analysis tools in a future release. ### Mark function as experimental To advertise that a function is experimental and the interface might change in the future, first add an experimental badge to the description: ```{r} #' @description #' `r lifecycle::badge("experimental")` ``` If the function is very experimental, you might want to add `@keywords internal` too. If you're willing to try an experimental lifecycle feature, add a call to `signal_stage()` in the body: ```{r} cool_function <- function() { lifecycle::signal_stage("experimental", "cool_function()") } ``` This signal isn't currently hooked up to any behaviour, but we plan to provide logging and analysis tools in a future release. ## Arguments ### Deprecate an argument, keeping the existing default Take this example where we want to deprecate `na.rm` in favour of always making it `TRUE.` ```{r} add_two <- function(x, y, na.rm = TRUE) { sum(x, y, na.rm = na.rm) } ``` First, add a badge to the argument description: ```{r} #' @param na.rm `r lifecycle::badge("deprecated")` `na.rm = FALSE` is no #' longer supported; this function will always remove missing values ``` And add a deprecation warning if `na.rm` is FALSE. In this case, there's no replacement to the behaviour, so we instead use `details` to provide a custom message: ```{r} add_two <- function(x, y, na.rm = TRUE) { if (!isTRUE(na.rm)) { lifecycle::deprecate_warn( when = "1.0.0", what = "add_two(na.rm)", details = "Ability to retain missing values will be dropped in next release." ) } sum(x, y, na.rm = na.rm) } add_two(1, NA, na.rm = TRUE) add_two(1, NA, na.rm = FALSE) ``` ### Deprecating an argument, providing a new default Alternatively, you can change the default value to `lifecycle::deprecated()` to make the deprecation status more obvious from the outside, and use `lifecycle::is_present()` to test whether or not the argument was provided. Unlike `missing()`, this works for both direct and indirect calls. ```{r} #' @importFrom lifecycle deprecated add_two <- function(x, y, na.rm = deprecated()) { if (lifecycle::is_present(na.rm)) { lifecycle::deprecate_warn( when = "1.0.0", what = "add_two(na.rm)", details = "Ability to retain missing values will be dropped in next release." ) } sum(x, y, na.rm = na.rm) } ``` The chief advantage of this technique is that users will get a warning regardless of what value of `na.rm` they use: ```{r} add_two(1, NA, na.rm = TRUE) add_two(1, NA, na.rm = FALSE) ``` ### Renaming an argument You may want to rename an argument if you realise you have made a mistake with the name of an argument. For example, you've realised that an argument accidentally uses `.` to separate a compound name, instead of `_`. You'll need to temporarily permit both arguments, generating a deprecation warning when the user supplies the old argument: ```{r} add_two <- function(x, y, na_rm = TRUE, na.rm = deprecated()) { if (lifecycle::is_present(na.rm)) { lifecycle::deprecate_warn("1.0.0", "add_two(na.rm)", "add_two(na_rm)") na_rm <- na.rm } add_two(x, y, na.rm = na_rm) } ``` ### Reducing allowed inputs to an argument To narrow the set of allowed inputs, call `deprecate_warn()` only when the user supplies the previously supported inputs. Make sure you preserve the previous behaviour: ```{r} add_two <- function(x, y) { if (length(y) != 1) { lifecycle::deprecate_warn("1.0.0", "foo(y = 'must be a scalar')") y <- sum(y) } x + y } add_two(1, 2) add_two(1, 1:5) ``` lifecycle/vignettes/manage.Rmd0000644000176200001440000000514614123356455016133 0ustar liggesusers--- title: "Manage lifecycle changes in functions you use" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Manage lifecycle changes in functions you use} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) options("lifecycle:::calling_package" = "tibble") ``` The lifecycle package uses warnings to tell you about deprecated functions. Deprecated functions will be removed in a future release, so it's good practice to eliminate the warnings as soon as you see them. For example, lets imagine your code uses `tibble::data_frame()`, which was deprecated in favour of `tibble()` in version 1.1.0. `data_frame()` now looks something like this: ```{r} data_frame <- function(...) { lifecycle::deprecate_warn("1.1.0", "data_frame()", "tibble()") tibble::tibble(...) } ``` That means if you use `data_frame()` in your own code you'll get a warning: ```{r} df1 <- data_frame(x = 1, y = 2) #> This warning is displayed once every 8 hours. #> Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated. df2 <- data_frame(a = "apple", b = "banana") ``` You'll notice that the warning only appears the first time we call it --- lifecycle only notifies you every 8 hours so it's not overly disruptive if you've used a deprecated function in many places. So how do you track down exactly where the warning came from? Firstly, you might notice the deprecation warning message includes the advice to call `lifecycle::last_lifecycle_warnings()`. That'll give you a list of all the deprecation warnings that have happened recently: ```{r, eval = FALSE} lifecycle::last_lifecycle_warnings() #> [[1]] #> #> message: `data_frame()` was deprecated in tibble 1.1.0. #> Please use `tibble()` instead. #> Backtrace: #> 1. global::data_frame(x = 1) ``` Each warning comes with a back trace that shows you the full sequence of calls that lead to the deprecated function. Alternatively, if you're ready to spend some time tracking down all your uses of deprecated functions, you can use the `lifecycle_verbosity` option to make deprecated functions warn every time: ```{r} options(lifecycle_verbosity = "warning") df1 <- data_frame(x = 1, y = 2) df2 <- data_frame(a = "apple", b = "banana") ``` Then use `lifecycle::last_lifecycle_warnings()` to track down the source. Alternatively, if you want to be really strict, you can turn all deprecation warnings into errors, forcing you to deal with them immediately: ```{r, error = TRUE} options("lifecycle_verbosity" = "error") df1 <- data_frame(x = 1, y = 2) ``` lifecycle/vignettes/lifecycle.graffle0000644000176200001440000001153014001553334017506 0ustar liggesusers]wy;?Iy=+f=zr=$b3AјA.QߏS+`sӱ?y!gغ34VT/?_ݔs3˜{zQ+> lfBU\ԚQ(>>Ldgg# hxg#sѨZOzqb4:\*/imՃ~fUQ,5z\eIormiw%nɺP`|G[Zx4W@St2T-_+ԉ>-w/Ն+e] teXZPV0TK H$\ rK\.*ٟ!|FlqC ő]hP%5,mBI5|RҼMMa=aIHa7B~('68nLZ*56M2U@VZ$iAw&%G_L K>~oad=W;snzf6FY@4;\wƶὄ~b4'0m+†Q7r ߈낌v?ϵkMh Z쑳! M,7 +mjz}HZ)HJiT(*r &$d#$hO%2>IO(ƼHl n:V+yF2s8ɚj˸{ݻ>;`ngzd5A]qm須NK)ԱS[̱"9}ۜJ93cD~w HdPD8Y Ld!Jy,˄M<'Q@,q@Nr0!YH ! XKҧCm>}(Ӭ[Q' /vөQ^f!)ayxdϿď [$tGHiԑiYϵn'R> (b3doo(aX)'MƩ)=p =/q sͶuZv4^|s^k9& B2Ѫ}zfs$k٬gwPhTWBZֲnzәʤ\k$jM]ԝ ^jqs_wY4uE@vNfYޗfFW KYswi@kG)zD)ܠ'ci n, H'* t;oU}+] 5&ծVVpWu\mH@ZGI@W'Кּ_gb6Juf#$i}՜0kM[j[z^5度5Cq^JY's2$JKXٜu< /Jt{ܜ$XnգnzsY^: Yڿ|6kkJ0XǦZJu p<+Z] HjYi 0^},<,~VA|M9[[͞>cBɭ r׿<(c `4oS7nGvdp'ʝփᙺve,xA﷮1$ގ]ðZpxi` 7~ϙ3v~QHD$m>܋:"\M2OCL~fg6{fg66|1cׇm`(TkH;/F3S^"v1gmNq2K"D?"(\C`ɷ^>9/,"P=)ΌWlzC4C'ϯ3ՏwbԣAQ"bIxgQ;Iگ0"yċxY)]opJ=g1S@/qb-X("SqRMI0/SA@8s=.\~FѸ'Jq)/ɼhm _]D7]UӰZ1nuf̼6f=1:"ƭկs"ڪy>l,J]f/Չ`.i, . `~W$2mkݪHea~Ƒ<剺 HJeQS+8_:[ZfvЯj 7h|gŀ)Й +y@.bU%/] qq˛=Cv3d` ~tOouAScB֔j[(*aǡΔҫ+\?(sִPsk}V)z2^un$4D y@<0@,^g_s2p*phgO-ߥ!NuW>Vz!d jH IR7uU@jjRÊRèyn u nWVi 5(i 5? $w1 RcqX5 n: R> jH݀5VEF>V@O2h֏c7Ƕwmėy(vv͆OJD+RA;S*tg9g)sl8lm6o9G$B!#75YInNowϏ˒Ҥݽ~:xR FVe9xvZ3hNԍ"uLcyT#,#Ri,[|rtuҴK[C4$ Yia LY lyV$vGglwtrw4yh_S~|Յ94 a*bf\uㅼQ=꟨wxafOͤ\㸅~;oZ`_`ϺcV eōȄ9q\rL;7Ȗ> u w/@{6 >|˙5-mpf=Y^9 ܄aS.jS Usage of the lifecycle package

Usage of the lifecycle package

library(lifecycle)

Use lifecycle to document the status of your exported functions and arguments. It provides a standard way to describe lifecycle stage in the documentation, and tools to encourage users away from deprecated functions.

Stages

The lifecycle stages for functions1 are summarised in the figure below.

The default development stage is Stable. A function is considered stable when the author is sufficiently happy with its interface and behaviour to share it with a large number of users. Stable features come with guarantees. If breaking changes are needed, they will occur gradually, through the deprecation process described below.

The stability of exported functions can be further refined based on the version number of the package they are exported from. In >=1.0.0 packages, the main functionality is considered “done” by the author. Breaking changes and deprecations are unlikely. In sub-1.0.0 packages, some parts of the package (internal backend, external UI, …) still need some work and changes are possible. Stable >=1.0.0 packages are the safest to depend and rely on.

Some packages and functions are published in an Experimental stage. Experimental features are made available so people can try them out and provide feedback, but the author makes no promises to avoid breaking changes. A deprecation process for breaking changes is not guaranteed (but may still happen if sufficiently many users depend on the feature).

End of life

There are two ways that a function might reach the end of its life: it might be superseded or deprecated.

  • A Superseded2 function has a better alternative available, but the function itself is not going away. While a superseded function will not receive new features, it will be kept working for the foreseeable future so that existing code does not need to change. A superseded feature preserves all the guarantees of a stable feature, and is just as safe to use.
  • A Deprecated function has a better alternative available and will be removed in the near future. Deprecated functions should also produce a message describing the alternative, and whenever you encounter a deprecation message in your code, you should make fixing it a priority.

Particularly important functions can have two additional stages in the deprecation cycle:

  • Soft-deprecated comes before deprecated. It’s a gentler form of deprecated designed to prevent new uses of a function and encourage package developers to move to a new for. Soft deprecated allows a package to change its extension interface in a way that downstream dependencies can adapt to before users are forced to change.

  • Defunct comes after deprecated. A defunct function is still exported, and a defunct argument is still part of the signature, but their usage will generate informative errors.

Sometimes the author of function is no longer certain that a function is the optimal approach, but doesn’t yet know how to do it better. These functions can be marked as Questioning to give users a heads up that the author has doubts about the function.

Documentation

Experimental, questioning, superseded, and deprecated (including soft-deprecated and defunct functions) should be clearly labelled in the document with a badge:

  • Call usethis::use_lifecycle() to import the badges in your package. Then use lifecycle::badge() to insert a badge:

    #' `r lifecycle::badge("experimental")
    #' `r lifecycle::badge("deprecated")

    This badge renders as text in non-HTML documentation. To document the status of a whole function, a good place to include the badge is at the top of the @description block. To document an argument, you can put the badge in the argument description.

    lifecycle::badge() is only ran by roxygen, so this is a build-time dependency. You don’t need to import lifecycle just to include badges in your documentation.

  • You should also include a brief description of why the function is in that state.

  • For superseded and deprecated functions, rewrite the examples showing how to translate from the old to new syntax.

  • For deprecated functions, add @keywords internal and update _pkgdown.yaml (if you use pkgdown), so that they’re no longer listed in documentation indexes.

Signals

Deprecated functions should clearly signal their deprecation status. There are three levels of verbosity:

  • Soft deprecation: Call deprecate_soft() to start warning users about the deprecation in the least disruptive way. This function only warns (a) users who try the feature from the global workspace (at most once every 8 hours), and (b) developers who directly use the feature (when running testthat tests). No warning is when the deprecated feature is called indirectly by another package.

  • Deprecation: Call deprecate_warn() to warn unconditionally about the deprecated feature. The warning is issued only once every 8 hours.

  • Defunct: Call deprecate_stop() to fail with an error.

The following sections describe the details when deprecating functions and arguments.

Functions

The first two arguments give the version where deprecation occurred3 and a description of what is deprecated:

deprecate_warn("1.0.0", "mypkg::foo()")
#> Warning: `foo()` is deprecated as of mypkg 1.0.0.
#> This warning is displayed once every 8 hours.
#> Call `lifecycle::last_warnings()` to see where this warning was generated.

Where possible, describe the replacement in the third argument:

deprecate_warn("1.0.0", "mypkg::foo()", "new()")
#> Warning: `foo()` is deprecated as of mypkg 1.0.0.
#> Please use `new()` instead.
#> This warning is displayed once every 8 hours.
#> Call `lifecycle::last_warnings()` to see where this warning was generated.

We explicitly mention the namespace in these examples, but you can typically omit the namespace because lifecycle will infer it from the calling environment. Specifying the namespace is mostly useful when the replacement is implemented in a different package.

# The new replacement
foobar_adder <- function(foo, bar) {
  foo + bar
}

# The old function still exported for compatibility
foobaz_adder <- function(foo, bar) {
  deprecate_warn("1.0.0", "foobaz_adder()", "foobar_adder()")
  foobar_adder(foo, bar)
}

Arguments

The syntax for deprecating arguments is similar:

deprecate_warn("1.0.0", "mypkg::foo(arg = )")
#> Warning: The `arg` argument of `foo()` is deprecated as of mypkg 1.0.0.
#> This warning is displayed once every 8 hours.
#> Call `lifecycle::last_warnings()` to see where this warning was generated.

deprecate_warn("1.0.0", "mypkg::foo(arg = )", "mypkg::foo(new = )")
#> Warning: The `arg` argument of `foo()` is deprecated as of mypkg 1.0.0.
#> Please use the `new` argument instead.
#> This warning is displayed once every 8 hours.
#> Call `lifecycle::last_warnings()` to see where this warning was generated.

An argument can be partially deprecated by disallowing certain input types:

deprecate_warn("1.0.0", "mypkg::foo(arg = 'must be a scalar integer')")
#> Warning: The `arg` argument of `foo()` must be a scalar integer as of mypkg 1.0.0.
#> This warning is displayed once every 8 hours.
#> Call `lifecycle::last_warnings()` to see where this warning was generated.

lifecycle also provides the deprecated() sentinel to use as default argument. This provides self-documentation for your users and makes it possible for external tools to determine which arguments are deprecated. Test whether the argument was supplied by the caller with lifecycle::is_present():

foobar_adder <- function(foo, bar, baz = deprecated()) {
  # Check if user has supplied `baz` instead of `bar`
  if (lifecycle::is_present(baz)) {

    # Signal the deprecation to the user
    deprecate_warn("1.0.0", "foobar_adder(baz = )", "foobar_adder(bar = )")

    # Deal with the deprecated argument for compatibility
    bar <- baz
  }

  foo + bar
}

Workflow

Where do these deprecation warnings come from?

Call lifecycle::last_warnings() to see backtraces for all the deprecation warnings that were issued during the last top-level command.

Bumping deprecation stage

Some manual search and replace is needed to bump the status of deprecated features. We recommend starting with defunct features and work your way up:

  1. Search for deprecate_stop() and remove the feature from the package. The feature is now archived.

  2. Search for deprecate_warn() and replace with deprecate_stop().

  3. Search for deprecate_soft() and replace with deprecate_warn().

  4. Call deprecate_soft() from newly deprecated functions.

Don’t forget to update the badges in the documentation topics.

Find out what deprecated features you rely on

Test whether your package depends on deprecated features directly or indirectly by setting the verbosity option in the tests/testthat.R file just before test_check() is called:

library(testthat)
library(mypackage)

options(lifecycle_verbosity = "error")
test_check("mypackage")

This forces all deprecated features to fail. You can also set the relevant options manually to force warnings or errors in your session:

# Force silence
options(lifecycle_verbosity = "quiet")

# Force warnings
options(lifecycle_verbosity = "warning")

# Force errors
options(lifecycle_verbosity = "error")

Forcing warnings can be useful in conjuction with last_warnings(), which prints backtraces for all the deprecation warnings issued during the last top-level command.

Test deprecated features

Test whether a deprecated feature still works by setting lifecycle_verbosity to "quiet":

test_that("`baz` argument of `foobar_adder()` still works", {
  withr::local_options(list(lifecycle_verbosity = "quiet"))
  foobar_adder(1, baz = 2)
})

You can also set up verbosity for a whole testthat file within setup() and teardown() blocks:

setup(options(lifecycle_verbosity = "quiet"))
teardown(options(lifecycle_verbosity = NULL))

Test that a feature is correctly deprecated with expect_deprecated() or expect_defunct():

test_that("`baz` argument of `foobar_adder()` is deprecated", {
  expect_deprecated(foobar_adder(1, baz = 2))
})

test_that("`foo()` is defunct", {
  expect_defunct(foo())
})

More control over verbosity can be exercised with the lifecycle_verbosity option. See ?verbosity.


  1. They can also be applied to individual arguments, or combinations of argument values.↩︎

  2. This stage was previously called retired.↩︎

  3. If the function goes through multiple deprecation stages, this stays the same.↩︎

lifecycle/R/0000755000176200001440000000000014123356455012422 5ustar liggesuserslifecycle/R/lifecycle-package.R0000644000176200001440000000060514123043512016061 0ustar liggesusers#' @keywords internal #' @import rlang "_PACKAGE" # The following block is used by usethis to automatically manage # roxygen namespace tags. Modify with care! ## usethis namespace: start ## usethis namespace: end NULL .onLoad <- function(lib, pkg) { run_on_load() } # FIXME: Export from rlang sexp_address <- NULL on_load( sexp_address <- env_get(ns_env("rlang"), "sexp_address") ) lifecycle/R/utils.R0000644000176200001440000000255614123043334013702 0ustar liggesusersupcase1 <- function(x) { substr(x, 1, 1) <- toupper(substr(x, 1, 1)) x } cat_line <- function(...) { cat(paste0(..., "\n", collapse = "")) } paste_line <- function(...) { paste(chr(...), collapse = "\n") } # nocov start has_crayon <- function() { is_installed("crayon") && crayon::has_color() } red <- function(x) if (has_crayon()) crayon::red(x) else x blue <- function(x) if (has_crayon()) crayon::blue(x) else x green <- function(x) if (has_crayon()) crayon::green(x) else x yellow <- function(x) if (has_crayon()) crayon::yellow(x) else x magenta <- function(x) if (has_crayon()) crayon::magenta(x) else x cyan <- function(x) if (has_crayon()) crayon::cyan(x) else x blurred <- function(x) if (has_crayon()) crayon::blurred(x) else x silver <- function(x) if (has_crayon()) crayon::silver(x) else x bold <- function(x) if (has_crayon()) crayon::bold(x) else x italic <- function(x) if (has_crayon()) crayon::italic(x) else x underline <- function(x) if (has_crayon()) crayon::underline(x) else x # nocov end lifecycle_abort <- function(x, env = parent.frame()) { x <- paste0("Internal error in lifecycle: ", glue::trim(x)) abort(glue::glue(x, .envir = env)) } `%<~~%` <- function(lhs, rhs, env = caller_env()) { env_bind_lazy(env, !!substitute(lhs) := !!substitute(rhs), .eval_env = env) } lifecycle/R/deprecated.R0000644000176200001440000002300414123356455014644 0ustar liggesusers#' Deprecate functions and arguments #' #' @description #' These functions provide three levels of verbosity for deprecated #' functions. Learn how to use them in `vignette("communicate")`. #' #' * `deprecate_soft()` warns only if the deprecated function is #' called from the global environment or from the package currently #' being tested. #' #' * `deprecate_warn()` warns unconditionally. #' #' * `deprecate_stop()` fails unconditionally. #' #' Warnings are only issued once every 8 hours to avoid overwhelming #' the user. Control with [`options(lifecycle_verbosity)`][verbosity]. #' #' @section Conditions: #' * Deprecation warnings have class `lifecycle_warning_deprecated`. #' * Deprecation errors have class `lifecycle_error_deprecated`. #' #' @param when A string giving the version when the behaviour was deprecated. #' @param what A string describing what is deprecated: #' #' * Deprecate a whole function with `"foo()"`. #' * Deprecate an argument with `"foo(arg)"`. #' * Partially deprecate an argument with #' `"foo(arg = 'must be a scalar integer')"`. #' #' You can optionally supply the namespace: `"ns::foo()"`, but this is #' usually not needed as it will be inferred from the caller environment. #' @param with An optional string giving a recommended replacement for the #' deprecated behaviour. This takes the same form as `what`. #' @param details In most cases the deprecation message can be automatically #' generated from `with`. When it can't, use `details` to provide a #' hand-written message. `details` can either be a single string or a #' character vector, which will be converted to a bulleted list. #' @param id The id of the deprecation. A warning is issued only once #' for each `id`. Defaults to the generated message, but you should #' give a unique ID when the message in `details` is built #' programmatically and depends on inputs, or when you'd like to #' deprecate multiple functions but warn only once for all of them. #' @param env,user_env Pair of environments that define where `deprecate_*()` #' was called (used to determine the package name) and where the function #' called the deprecating function was called (used to determine if #' `deprecate_soft()` should message). #' #' These are only needed if you're calling `deprecate_*()` from an internal #' helper, in which case you should forward `env = caller_env()` and #' `user_env = caller_env(2)`. #' @return `NULL`, invisibly. #' #' @seealso [lifecycle()] #' #' @examples #' # A deprecated function `foo`: #' deprecate_warn("1.0.0", "foo()") #' #' # A deprecated argument `arg`: #' deprecate_warn("1.0.0", "foo(arg)") #' #' # A partially deprecated argument `arg`: #' deprecate_warn("1.0.0", "foo(arg = 'must be a scalar integer')") #' #' # A deprecated function with a function replacement: #' deprecate_warn("1.0.0", "foo()", "bar()") #' #' # A deprecated function with a function replacement from a #' # different package: #' deprecate_warn("1.0.0", "foo()", "otherpackage::bar()") #' #' # A deprecated function with custom message: #' deprecate_warn( #' when = "1.0.0", #' what = "foo()", #' details = "Please use `otherpackage::bar(foo = TRUE)` instead" #' ) #' #' # A deprecated function with custom bulleted list: #' deprecate_warn( #' when = "1.0.0", #' what = "foo()", #' details = c( #' x = "This is dangerous", #' i = "Did you mean `safe_foo()` instead?" #' ) #' ) #' @export deprecate_soft <- function(when, what, with = NULL, details = NULL, id = NULL, env = caller_env(), user_env = caller_env(2)) { msg <- NULL # trick R CMD check msg %<~~% lifecycle_message(when, what, with, details, env, "deprecate_soft") signal_stage("deprecated", what) verbosity <- lifecycle_verbosity() if (verbosity == "quiet") { NULL } else if (verbosity %in% "warning" || (is_string(verbosity, "default") && env_inherits_global(user_env))) { trace <- trace_back(bottom = caller_env()) deprecate_warn0(msg, trace) } else if (verbosity == "error") { deprecate_stop0(msg) } else { deprecate_soft0(msg) } invisible(NULL) } #' @rdname deprecate_soft #' @export deprecate_warn <- function(when, what, with = NULL, details = NULL, id = NULL, env = caller_env()) { msg <- NULL # trick R CMD check msg %<~~% lifecycle_message(when, what, with, details, env, "deprecate_warn") signal_stage("deprecated", what) verbosity <- lifecycle_verbosity() if (verbosity == "quiet") { NULL } else if (verbosity == "warning") { trace <- trace_back(bottom = caller_env()) deprecate_warn0(msg, trace) } else if (verbosity == "error") { deprecate_stop0(msg) } else { id <- id %||% msg if (needs_warning(id)) { # Prevent warning from being displayed again env_poke(deprecation_env, id, Sys.time()) footer <- paste_line( silver("This warning is displayed once every 8 hours."), silver("Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.") ) trace <- trace_back(bottom = caller_env()) deprecate_warn0(msg, trace, footer) } } invisible(NULL) } #' @rdname deprecate_soft #' @export deprecate_stop <- function(when, what, with = NULL, details = NULL, env = caller_env()) { msg <- NULL # trick R CMD check msg %<~~% lifecycle_message(when, what, with, details, env, "deprecate_stop") signal_stage("deprecated", what) deprecate_stop0(msg) } # Signals ----------------------------------------------------------------- deprecate_soft0 <- function(msg) { signal(msg, "lifecycle_soft_deprecated") } deprecate_warn0 <- function(msg, trace = NULL, footer = NULL) { wrn <- new_deprecated_warning(msg, trace, footer = footer) # Record muffled warnings if testthat is running because it # muffles all warnings but we still want to examine them after a # run of `devtools::test()` maybe_push_warning <- function() { if (Sys.getenv("TESTTHAT_PKG") != "") { push_warning(wrn) } } withRestarts(muffleWarning = maybe_push_warning, { signalCondition(wrn) push_warning(wrn) warning(wrn) }) } deprecate_stop0 <- function(msg) { stop(cnd( c("lifecycle_error_deprecated", "defunctError", "error", "condition"), old = NULL, new = NULL, package = NULL, message = msg )) } # Messages ---------------------------------------------------------------- lifecycle_message <- function(when, what, with = NULL, details = NULL, env = caller_env(2), signaller = "signal_lifecycle") { if (!is_string(when)) { lifecycle_abort("`when` must be a string") } details <- details %||% chr() if (!is.character(details)) { lifecycle_abort("`details` must be a character vector") } if (length(details) > 1) { details <- format_error_bullets(details) } what <- spec(what, env, signaller) msg <- lifecycle_message_what(what, when) if (!is_null(with)) { with <- spec(with, NULL, signaller) msg <- paste0(msg, "\n", lifecycle_message_with(with, what)) } paste_line(msg, details) } lifecycle_message_what <- function(what, when) { glue_what <- function(x) glue::glue_data(what, x) what$fn <- fun_label(what$fn) if (is_null(what$arg)) { if (what$from == "deprecate_stop") { glue_what("{ fn } was deprecated in { pkg } { when } and is now defunct.") } else { glue_what("{ fn } was deprecated in { pkg } { when }.") } } else { if (what$from == "deprecate_stop" && is_null(what$reason)) { glue_what("The `{ arg }` argument of { fn } was deprecated in { pkg } { when } and is now defunct.") } else { what$reason <- what$reason %||% "is deprecated" glue_what("The `{ arg }` argument of { fn } { reason } as of { pkg } { when }.") } } } fun_label <- function(fn) { if (grepl("^`", fn)) { fn } else { paste0("`", fn , "()`") } } lifecycle_message_with <- function(with, what) { glue_with <- function(x) glue::glue_data(with, x) if (!is_null(with$pkg) && what$pkg != with$pkg) { with$fn <- glue_with("{ pkg }::{ fn }") } if (is_null(with$arg)) { glue_with("Please use `{ fn }()` instead.") } else if (what$fn == with$fn) { glue_with("Please use the `{ arg }` argument instead.") } else { glue_with("Please use the `{ arg }` argument of `{ fn }()` instead.") } } # Helpers ----------------------------------------------------------------- env_inherits_global <- function(env) { # `topenv(emptyenv())` returns the global env. Return `FALSE` in # that case to allow passing the empty env when the # soft-deprecation should not be promoted to deprecation based on # the caller environment. if (is_reference(env, empty_env())) { return(FALSE) } is_reference(topenv(env), global_env()) } needs_warning <- function(id) { if (!is_string(id)) { lifecycle_abort("`id` must be a string") } last <- deprecation_env[[id]] if (is_null(last)) { return(TRUE) } if (!inherits(last, "POSIXct")) { lifecycle_abort("Expected `POSIXct` value in `needs_warning()`.") } # Warn every 8 hours (Sys.time() - last) > (8 * 60 * 60) } lifecycle/R/badge.R0000644000176200001440000000424214030346463013604 0ustar liggesusers#' Embed a lifecycle badge in documentation #' #' @description #' #' To include lifecycle badges in your documentation: #' #' 1. Call `usethis::use_lifecycle()` to copy the badge images into the #' `man/` folder of your package. #' #' 2. Call `lifecycle::badge()` inside R backticks to insert a #' lifecycle badge: #' #' ``` #' #' `r lifecycle::badge("experimental")` #' #' `r lifecycle::badge("deprecated")` #' #' `r lifecycle::badge("superseded")` #' ``` #' #' If the deprecated feature is a function, a good place for this #' badge is at the top of the topic description. If it is an argument, #' you can put the badge in the argument description. #' #' The badge is displayed as an image in the HTML version of the #' documentation and as text otherwise. #' #' `lifecycle::badge()` is run by roxygen at build time so you don't need #' to add lifecycle to `Imports:` just to use the badges. However, it's still #' good practice to add to `Suggests:` so that it will be available to #' package developers. #' #' @section Badges: #' * `r lifecycle::badge("experimental")` `lifecycle::badge("experimental")` #' * `r lifecycle::badge("stable")` `lifecycle::badge("stable")` #' * `r lifecycle::badge("superseded")` `lifecycle::badge("superseded")` #' * `r lifecycle::badge("deprecated")` `lifecycle::badge("deprecated")` #' #' The meaning of these stages is described in #' `vignette("stages")`. #' #' @param stage A lifecycle stage as a string. Must be one of #' `"experimental"`, `"stable"`, `"superseded"`, or `"deprecated"`. #' @return An `Rd` expression describing the lifecycle stage. #' #' @export badge <- function(stage) { old <- c("maturing", "questioning", "soft-deprecated", "defunct", "retired") if (!stage %in% old) { stage <- arg_match0(stage, c("experimental", "stable", "superseded", "deprecated") ) } url <- paste0("https://lifecycle.r-lib.org/articles/stages.html#", stage) html <- sprintf( "\\href{%s}{\\figure{%s}{options: alt='[%s]'}}", url, file.path(sprintf("lifecycle-%s.svg", stage)), upcase1(stage) ) text <- sprintf("\\strong{[%s]}", upcase1(stage)) sprintf("\\ifelse{html}{%s}{%s}", html, text) } lifecycle/R/expect.R0000644000176200001440000000266213777032236014045 0ustar liggesusers#' Does expression produce lifecycle warnings or errors? #' #' @description #' These functions are equivalent to [testthat::expect_warning()] and #' [testthat::expect_error()] but check specifically for lifecycle #' warnings or errors. #' #' To test whether a deprecated feature still works without causing a #' deprecation warning, set the `lifecycle_verbosity` option to #' `"quiet"`. #' #' ``` #' test_that("feature still works", { #' withr::local_options(lifecycle_verbosity = "quiet") #' expect_true(my_deprecated_function()) #' }) #' ``` #' #' @param expr Expression that should produce a lifecycle warning or #' error. #' @param regexp Optional regular expression matched against the #' expected warning message. #' @inheritParams testthat::expect_warning #' #' @details #' `expect_deprecated()` sets the [lifecycle_verbosity][verbosity] #' option to `"warning"` to enforce deprecation warnings which are #' otherwise only shown once every 8 hours. #' #' @export expect_deprecated <- function(expr, regexp = NULL, ...) { local_options(lifecycle_verbosity = "warning") if (!is_null(regexp) && is_na(regexp)) { abort("`regexp` can't be `NA`.") } testthat::expect_warning( {{ expr }}, regexp = regexp, class = "lifecycle_warning_deprecated", ... ) } #' @rdname expect_deprecated #' @export expect_defunct <- function(expr) { testthat::expect_error( {{ expr }}, class = "lifecycle_error_deprecated" ) } lifecycle/R/aaa.R0000644000176200001440000000133514123043352013256 0ustar liggesuserson_load <- function(expr, env = parent.frame(), ns = topenv(env)) { expr <- substitute(expr) force(env) callback <- function() eval_bare(expr, env) ns$.__rlang_hook__. <- c(ns$.__rlang_hook__., list(callback)) } run_on_load <- function(env = parent.frame()) { ns <- topenv(env) hook <- ns$.__rlang_hook__. env_unbind(ns, ".__rlang_hook__.") # FIXME: Transform to `while` loop to allow hooking into on-load # from an on-load hook? for (callback in hook) { callback() } ns$.__rlang_hook__. <- NULL } replace_from <- function(pkg, what, to = topenv(caller_env())) { if (what %in% getNamespaceExports(pkg)) { env <- ns_env(pkg) } else { env <- to } env_get(env, what, inherit = TRUE) } lifecycle/R/signal.R0000644000176200001440000000331214012426337014013 0ustar liggesusers#' Signal other experimental or superseded features #' #' @description #' `r badge("experimental")` #' #' `signal_stage()` allows you to signal life cycle stages other than #' deprecation (for which you should use [deprecate_warn()] and friends). #' There is no behaviour associated with this signal, but in the future #' we will provide tools to log and report on usage of experimental and #' superseded functions. #' #' @param stage Life cycle stage, either "experimental" or "superseded". #' @param what String describing what feature the stage applies too, using #' the same syntax as [deprecate_warn()]. #' @inheritParams deprecate_warn #' @export #' @examples #' foofy <- function(x, y, z) { #' signal_stage("experimental", "foofy()") #' x + y / z #' } #' foofy(1, 2, 3) signal_stage <- function(stage, what, env = caller_env()) { stage <- arg_match(stage, c("experimental", "superseded", "deprecated")) what <- spec(what, env = env) if (is_null(what$arg)) { msg <- glue::glue_data(what, "{fn}() is {stage}") } else { msg <- glue::glue_data(what, "{fn}(arg) is {stage}") } signal(msg, "lifecycle_stage", stage = stage, package = what$pkg, function_nm = what$fn, argument = what$arg, reason = what$reason ) } #' Deprecated funtions for signalling experimental and lifecycle stages #' #' @description #' `r badge("deprecated")` #' Please use [signal_stage()] instead #' @keywords internal #' @export signal_experimental <- function(when, what, env = caller_env()) { signal_stage("experimental", what, env = env) } #' @rdname signal_experimental #' @export signal_superseded <- function(when, what, with = NULL, env = caller_env()) { signal_stage("superseded", what, env = env) } lifecycle/R/spec.R0000644000176200001440000000535114012426337013475 0ustar liggesusers spec <- function(spec, env = caller_env(), signaller = "signal_lifecycle") { what <- spec_what(spec, "spec", signaller) fn <- spec_fn(what$call) arg <- spec_arg(what$call, signaller) reason <- spec_reason(what$call, signaller) if (is_null(what$pkg) && !is.null(env)) { pkg <- spec_package(env, signaller = signaller) } else { pkg <- what$pkg } list( fn = fn, arg = arg, pkg = pkg, reason = reason, from = signaller ) } spec_what <- function(what, arg, signaller) { if (is_string(what)) { call <- parse_expr(what) } else { lifecycle_abort("`what` must be a string.") } if (!is_call(call)) { what <- as_string(what) lifecycle_abort( " `what` must have function call syntax. # Good: { signaller }(\"{what}()\") # Bad: { signaller }(\"{what}\") " ) } head <- node_car(call) if (is_call(head, "::")) { pkg <- as_string(node_cadr(head)) call[[1]] <- node_cadr(node_cdr(head)) } else { pkg <- NULL } list(pkg = pkg, call = call) } spec_fn <- function(call) { fn <- node_car(call) if (!is_symbol(fn) && !is_call(fn, "$")) { lifecycle_abort("`what` must be a function or method call.") } # Deparse so non-syntactic names are backticked expr_deparse(fn) } spec_arg <- function(call, signaller) { arg <- node_cdr(call) if (is_null(arg)) { return(NULL) } if (length(arg) != 1L) { fn <- as_label(node_car(call)) n <- length(arg) lifecycle_abort("Function in `what` ({fn}) must have 1 argument, not {n}.") } if (is_null(node_tag(arg))) { as_string(node_car(arg)) } else { as_string(node_tag(arg)) } } spec_reason <- function(call, signaller) { arg <- node_cdr(call) if (is_null(arg)) { return(NULL) } if (is_null(node_tag(arg))) { return(NULL) } if (is_missing(node_car(arg))) { return(NULL) } if (is_string(node_car(arg))) { return(node_car(arg)) } fn <- expr_deparse(node_car(call)) lifecycle_abort( " `what` must contain reason as a string on the RHS of `=`. # Good: {signaller}(\"{fn}(arg = 'must be a string')\") # Bad: {signaller}(\"{fn}(arg = 42)\") " ) } spec_package <- function(env, signaller) { env <- topenv(env) if (is_reference(env, global_env())) { # Convenient for experimenting interactively return(getOption("lifecycle:::calling_package", "")) } if(is_namespace(env)) { return(ns_env_name(env)) } lifecycle_abort( " Can't detect the package of the deprecated function. Please mention the namespace: # Good: { signaller }(what = \"namespace::myfunction()\") # Bad: { signaller }(what = \"myfunction()\") " ) } lifecycle/R/arg.R0000644000176200001440000000256513617311420013314 0ustar liggesusers#' Mark an argument as deprecated #' #' Signal deprecated argument by using self-documenting sentinel #' `deprecated()` as default argument. Test whether the caller has #' supplied the argument with `is_present()`. #' #' @section Magical defaults: #' #' We recommend importing `lifecycle::deprecated()` in your namespace #' and use it without the namespace qualifier. #' #' In general, we [advise #' against](https://principles.tidyverse.org/def-magical.html) such #' magical defaults, i.e. defaults that cannot be evaluated by the #' user. In the case of `deprecated()`, the trade-off is worth it #' because the meaning of this default is obvious and there is no #' reason for the user to call `deprecated()` themselves. #' #' @examples #' foobar_adder <- function(foo, bar, baz = deprecated()) { #' # Check if user has supplied `baz` instead of `bar` #' if (lifecycle::is_present(baz)) { #' #' # Signal the deprecation to the user #' deprecate_warn("1.0.0", "foo::bar_adder(baz = )", "foo::bar_adder(bar = )") #' #' # Deal with the deprecated argument for compatibility #' bar <- baz #' } #' #' foo + bar #' } #' #' foobar_adder(1, 2) #' foobar_adder(1, baz = 2) #' #' @export deprecated <- function() { missing_arg() } #' @rdname deprecated #' @param arg A `deprecated()` function argument. #' @export is_present <- function(arg) { !is_missing(maybe_missing(arg)) } lifecycle/R/verbosity.R0000644000176200001440000000401314123356455014571 0ustar liggesusers#' Control the verbosity of deprecation signals #' #' @description #' #' There are 3 levels of verbosity for deprecated functions: silence, #' warning, and error. Since the lifecycle package avoids disruptive #' warnings, the default level of verbosity depends on the lifecycle #' stage of the deprecated function, on the context of the caller #' (global environment or testthat unit tests cause more warnings), #' and whether the warning was already issued (see the help for #' [deprecation functions][deprecate_soft]). #' #' You can control the level of verbosity with the global option #' `lifecycle_verbosity`. It can be set to: #' #' * `"default"` or `NULL` for the default non-disruptive settings. #' #' * `"quiet"`, `"warning"` or `"error"` to force silence, warnings or #' errors for deprecated functions. #' #' Note that functions calling [deprecate_stop()] invariably throw #' errors. #' #' @examples #' if (rlang::is_installed("testthat")) { #' library(testthat) #' #' mytool <- function() { #' deprecate_soft("1.0.0", "mytool()") #' 10 * 10 #' } #' #' # Forcing the verbosity level is useful for unit testing. You can #' # force errors to test that the function is indeed deprecated: #' test_that("mytool is deprecated", { #' rlang::local_options(lifecycle_verbosity = "error") #' expect_error(mytool(), class = "defunctError") #' }) #' #' # Or you can enforce silence to safely test that the function #' # still works: #' test_that("mytool still works", { #' rlang::local_options(lifecycle_verbosity = "quiet") #' expect_equal(mytool(), 100) #' }) #' } #' #' @name verbosity NULL lifecycle_verbosity <- function() { opt <- peek_option("lifecycle_verbosity") %||% "default" if (!is_string(opt, c("quiet", "default", "warning", "error"))) { options(lifecycle_verbosity = "default") warn(glue::glue( " The `lifecycle_verbosity` option must be set to one of: \"quiet\", \"default\", \"warning\", or \"error\". Resetting to \"default\". " )) } opt } lifecycle/R/lint.R0000644000176200001440000001211414123037657013512 0ustar liggesusers# Retrieve the lifecycle status defined in each Rd file db_lifecycle <- function(db) { lifecycle_patterns <- paste0("(?:", paste(collapse = "|", c("lifecycle::badge\\([\\\\]\"", "rlang:::lifecycle\\([\\\\]\"", "list\\(\"lifecycle-", "https://www.tidyverse.org/lifecycle/#" )), ")([\\w-]+)" ) desc <- lapply(db, asNamespace("tools")$.Rd_get_metadata, "description") lapply(desc, function(x) { res <- regexpr(lifecycle_patterns, x, perl = TRUE) starts <- attr(res, "capture.start") ends <- starts + attr(res, "capture.length") - 1 substring(x, starts, ends) }) } # Retrieve the functions listed in the usage for each Rd file in the database db_function <- function(db) { usage <- lapply(db, asNamespace("tools")$.Rd_get_section, "usage") lapply(usage, get_usage_function_names) } #' @param package The name of an installed package. #' @param which The lifecycle statuses to retrieve. #' Include `NA` if you want to include functions without a specified lifecycle #' status in the results. #' @export #' @rdname lint_lifecycle pkg_lifecycle_statuses <- function(package, which = c("superseded", "deprecated", "questioning", "defunct", "experimental", "soft-deprecated", "retired")) { check_installed("vctrs") which <- match.arg(which, several.ok = TRUE) stopifnot(is_string(package)) db <- tools::Rd_db(package) lc <- db_lifecycle(db) funs <- db_function(db) res <- mapply(function(lc, f) data.frame(fun = f, lifecycle = rep(lc, length(f)), stringsAsFactors = FALSE), lc, funs, SIMPLIFY = FALSE) res <- vctrs::vec_rbind(!!!res) # Filter funs without a lifecycle if (!NA %in% which) { res <- res[!is.na(res$lifecycle), ] } # Filter funs without a function name res <- res[nzchar(res$fun), ] # Filter method definitions res <- res[grep("[\\\\]method\\{", res$fun, invert = TRUE), ] # filter lifecycles not in which res <- res[res$lifecycle %in% which, ] if (nrow(res) == 0) { return(data.frame(package = character(), fun = character(), lifecycle = character())) } res$package <- package res[c("package", "fun", "lifecycle")] } get_usage_function_names <- function(x) { if (!length(x)) { character(1) } else { res <- asNamespace("tools")$.parse_usage_as_much_as_possible(x) vapply(res, function(x) { if (is.call(x)) as.character(x[[1]]) else character(1) }, character(1)) } } #' Lint usages of functions that have a non-stable life cycle. #' #' - `lint_lifecycle` dynamically queries the package documentation for packages #' in `packages` for lifecycle annotations and then searches the directory in #' `path` for usages of those functions. #' - `lint_tidyverse_lifecycle` is a convenience function to call `lint_lifecycle` #' for all the packages in the tidyverse. #' - `pkg_lifecycle_statuses` returns a data frame of functions with lifecycle #' annotations for an installed package. #' #' @param packages One or more installed packages to query for lifecycle statuses. #' @param path The directory path to the files you want to search. #' @param pattern Any files matching this pattern will be searched. The default #' searches any files ending in `.R` or `.Rmd`. #' @export lint_lifecycle <- function(packages, path = ".", pattern = "[.][Rr](md)?", which = c("superseded", "deprecated", "questioning", "defunct", "experimental", "soft-deprecated", "retired")) { which <- match.arg(which, several.ok = TRUE) check_installed(c("lintr", "vctrs")) life_cycles <- vctrs::vec_rbind(!!!lapply(packages, pkg_lifecycle_statuses, which = which)) msgs <- sprintf("`%s::%s` is %s", life_cycles$package, life_cycles$fun, life_cycles$lifecycle) lifecycle_linter <- function(source_file) { lapply( lintr::ids_with_token(source_file, "SYMBOL_FUNCTION_CALL", fun = `%in%`), function(id) { token <- lintr::with_id(source_file, id) fun_name <- token[["text"]] has_lifecycle_fun <- fun_name == life_cycles$fun if (any(has_lifecycle_fun)) { line_num <- token[["line1"]] start_col_num <- token[["col1"]] end_col_num <- token[["col2"]] # In case more than one lifecycle function matches, we only take the first one. msg <- msgs[has_lifecycle_fun][[1]] lintr::Lint( filename = source_file[["filename"]], line_number = line_num, column_number = start_col_num, type = "warning", message = msg, line = source_file[["lines"]][[as.character(line_num)]], ranges = list(c(start_col_num, end_col_num)) ) } } ) } lintr::lint_dir(path = path, pattern = pattern, linters = lifecycle_linter) } #' @rdname lint_lifecycle #' @export lint_tidyverse_lifecycle <- function(path = ".", pattern = "[.][Rr](md)?", which = c("superseded", "deprecated", "questioning", "defunct", "experimental", "soft-deprecated", "retired")) { which <- match.arg(which, several.ok = TRUE) check_installed(c("lintr", "vctrs", "tidyverse")) lint_lifecycle(packages = tidyverse::tidyverse_packages(), path = path, which = which) } lifecycle/R/warning.R0000644000176200001440000000501414123356455014212 0ustar liggesusers#' Display last deprecation warnings #' #' @description #' #' `last_lifecycle_warnings()` returns a list of all warnings that #' occurred during the last top-level R command, along with a #' backtrace. #' #' Use `print(last_lifecycle_warnings(), simplify = level)` to control #' the verbosity of the backtrace. The `simplify` argument supports #' one of `"branch"` (the default), `"collapse"`, and `"none"` (in #' increasing order of verbosity). #' #' @examples #' # These examples are not run because `last_lifecycle_warnings()` does not #' # work well within knitr and pkgdown #' \dontrun{ #' #' f <- function() invisible(g()) #' g <- function() list(h(), i()) #' h <- function() deprecate_warn("1.0.0", "this()") #' i <- function() deprecate_warn("1.0.0", "that()") #' f() #' #' # Print all the warnings that occurred during the last command: #' last_lifecycle_warnings() #' #' #' # By default, the backtraces are printed in their simplified form. #' # Use `simplify` to control the verbosity: #' print(last_lifecycle_warnings(), simplify = "none") #' #' } #' @export last_lifecycle_warnings <- function() { warnings_env$warnings } new_deprecated_warning <- function(msg, trace, ...) { warning_cnd( "lifecycle_warning_deprecated", message = msg, trace = trace, internal = list(...) ) } #' @export conditionMessage.lifecycle_warning_deprecated <- function(c) { paste_line( c$message, c$internal$footer ) } #' @export print.lifecycle_warning_deprecated <- function(x, ..., simplify = c("branch", "collapse", "none")) { cat_line(bold("")) message <- x$message if (is_string(message) && nzchar(message)) { cat_line(sprintf("message: %s", italic(message))) } print_trace(x, ..., simplify = simplify) invisible(x) } print_trace <- function(cnd, ..., simplify = c("branch", "collapse", "none")) { trace <- cnd$trace if (!is_null(trace)) { cat_line(bold("Backtrace:")) cat_line(red(format(trace, ..., simplify = simplify))) } } warnings_env <- env(empty_env()) init_warnings <- function() { warnings_env$last_top_frame <- NULL warnings_env$warnings <- list() } init_warnings() push_warning <- function(wrn) { current <- sexp_address(sys.frame(1)) if (identical(warnings_env$last_top_frame, current)) { warnings_env$warnings <- c(warnings_env$warnings, list(wrn)) } else { warnings_env$last_top_frame <- current warnings_env$warnings <- list(wrn) } } # Contains unique IDs of deprecated features so we don't warn multiple times deprecation_env <- env(empty_env()) lifecycle/NEWS.md0000644000176200001440000000631514123357722013322 0ustar liggesusers# lifecycle 1.0.1 * `deprecate_soft()` now follows the verbosity option when called from the global environment (#113). * `last_warnings()` has been renamed to `last_lifecycle_warnings()` and `last_warning()` has been removed. This is for compatibility with the future `rlang::last_warnings()` function to be released in the next rlang version. # lifecycle 1.0.0 * New vignettes: * `vignette("stages")` describes the lifecycle stages * `vignette("manage")` teaches you how to manage lifecycle changes in functions you _use_. * `vignette("communicate")` shows how to use lifecycle in functions that you _write_. * In `deprecate_soft()`, `deprecate_warn()`, and `deprecate_stop()`: * You can deprecate an argument with `foo(arg)` instead of `foo(arg =)` (#78). This syntax is similar in spirit to the formal arguments of function definitions. * You can deprecate R6 methods by using `class$method()` (#54). * A character vector `details` is now converted into a bulleted list (#55). * Messages for non-prefix functions (like "`x<-`()" and "`%>%`()") look a little nicer (#95). * Manually printed warnings now omit the advice footer (#68). * Experimental `signal_stage()` can be used to signal that a function is experimental or superseded. These signals are not currently hooked up to any behaviour, but we'll add tools in a future release (#44). * `lifecycle_cnd_data()` has been removed; as far as I can tell it wasn't used by anyone. # lifecycle 0.2.0 * Lifecycle warnings are now displayed once every 8 hours. * Added experimental `signal_experimental()` and `signal_superseded()` functions. * Added the "superseded" lifecycle stage to the documentation. * `deprecate_stop()` now mentions that function is defunct (#28). * New `expect_deprecated()` and `expect_defunct()` functions for testting lifecycle warnings and errors. `expect_deprecated()` automatically sets the `lifecycle_verbosity` option to `"warning"` to enforce warnings at each invokation rather than once per session. * New syntax `"foo(arg = 'can\\'t be a baz')"` to describe that specific inputs for an argument are deprecated (#30, @krlmlr). * New `is_present()` function to test whether the caller has supplied a `deprecated()` function. # lifecycle 0.1.0 * Deprecated functions under the control of the developer now warn repeatedly in unit tests. * Deprecation warnings now record a backtrace. Call `lifecycle::last_lifecycle_warnings()` and `lifecycle::last_warning()` to print the warnings that occurred during the last command, along with their backtraces. * The naming scheme of signaller functions has been simplified: - `signal_soft_deprecated()` is now `deprecate_soft()`. - `warn_deprecated()` is now `deprecate_warn()`. - `stop_defunct()` is now `deprecate_stop()`. * The signaller functions now take a version and two descriptors for the deprecated feature and its replacement (the latter is optional). The deprecation message is built from these components. You can pass a `details` argument to append additional information to the generated deprecation message. * Helpers from rlang's `compat-lifecycle.R` drop-in file are now exported in this package. lifecycle/MD50000644000176200001440000001233114123367372012531 0ustar liggesusers64d71d3c2ffd8296e4c286b63837f8dc *DESCRIPTION 5174dfc514f0941d2edd4b0b4c9941dd *LICENSE 95a41032c96c29ff675f7d6bef26e131 *NAMESPACE f05587fb1c6e5292dd8f71b9e9504585 *NEWS.md c036d431adbbc769814b895287e1c191 *R/aaa.R 8015581579386a8355ed75817f1e8989 *R/arg.R d1736a1f3dcd1cc50f1f905250734d0f *R/badge.R a9f6145673ff34a63bf3b69eb8082276 *R/deprecated.R 7490d87bf8ed69a1b1fe43bfed32ad1d *R/expect.R f74bc8e50a7833d5f402472bc8a7c926 *R/lifecycle-package.R fd5f78b7d241543870a757046ab611e0 *R/lint.R a02d7dff14296a1902a4ff54ec0a30a9 *R/signal.R e21fcc48136f0c2fa5216dfaa40d6b5f *R/spec.R 2bce7578fa7e29848456c0e717c613ff *R/utils.R 090423132473930e40e0d6bdd214fd31 *R/verbosity.R f1575f5bad12352c36e7251a1ee10148 *R/warning.R a74127a8bc3fe2322cfc07ec4947d853 *README.md 2ee2f28529932b65a636bdbb64212ba0 *build/vignette.rds c5f30907056697e0ea5291658ec52b80 *inst/doc/communicate.R 513c7a7adacddad5d31a17c424a2fbaa *inst/doc/communicate.Rmd 84446395f468531c3042d6369b9300dc *inst/doc/communicate.html 012288ca3a1c7bce0d99c504347f8e4e *inst/doc/manage.R f9cab395a969e8dc9f4384ed7da86390 *inst/doc/manage.Rmd 22325dda47a70d9716b548ac8d3becc3 *inst/doc/manage.html b9e774b44fa390a568eb09dd2e0d855c *inst/doc/stages.R 624be490da0e5b2df7cd0de381f06370 *inst/doc/stages.Rmd 584d72f31876f69cd90912d63ee7aae2 *inst/doc/stages.html e9bfe31f2f6a8cd93dfe11e2811955e9 *man/badge.Rd 40c33aecc90847d3895998a67b818ba0 *man/deprecate_soft.Rd cea61a230c95b8eeffcbe43a0a965da0 *man/deprecated.Rd 3915ab9a1ce77a0932be969f36ebfbbd *man/expect_deprecated.Rd cb1e46f469cfbbbde29c8b5113e1d789 *man/figures/lifecycle-archived.svg c0d2e5a54f1fa4ff02bf9533079dd1f7 *man/figures/lifecycle-defunct.svg a1b8c987c676c16af790f563f96cbb1f *man/figures/lifecycle-deprecated.svg c3978703d8f40f2679795335715e98f4 *man/figures/lifecycle-experimental.svg 952b59dc07b171b97d5d982924244f61 *man/figures/lifecycle-maturing.svg 27b879bf3677ea76e3991d56ab324081 *man/figures/lifecycle-questioning.svg 46de21252239c5a23d400eae83ec6b2d *man/figures/lifecycle-retired.svg 6902bbfaf963fbc4ed98b86bda80caa2 *man/figures/lifecycle-soft-deprecated.svg 53b3f893324260b737b3c46ed2a0e643 *man/figures/lifecycle-stable.svg d519c3283da75955a8cbaec351380d28 *man/figures/lifecycle-superseded.svg 231a2dcdff1641fb4d99fbe0ed4e62f9 *man/last_lifecycle_warnings.Rd c9650caf5932c76250e4c1159601622b *man/lifecycle-package.Rd bc1b18700a9da53baada60ced89be5a1 *man/lint_lifecycle.Rd 155bedac3338ac2404018d81751c5b18 *man/macros/lifecycle.Rd 541ec016bd81fe5d1ad41430cfb6412a *man/signal_experimental.Rd 5150f6457494baaef009d564e3a3a990 *man/signal_stage.Rd 4f170b39904f2bd532010064fc7d1761 *man/verbosity.Rd ed81c7165f2a80934e2a76d4262b843a *tests/testthat.R 90d78abd898cefa0def6c0a443c920d2 *tests/testthat/_snaps/badge.md 2eeb148b222568b301d94ab546d27a7d *tests/testthat/_snaps/badge.new.md 005d4332761595785a3219309ce35a41 *tests/testthat/_snaps/deprecated.md 8ce3237ebb2d87bc94098328d299fefe *tests/testthat/_snaps/deprecated.new.md 283b84637f13b627d1571bf16f37197c *tests/testthat/_snaps/signal.md be50d0db2c3e3007658f53c8f4390152 *tests/testthat/_snaps/spec.md 19d00073180f3414cebc18408ff57e07 *tests/testthat/_snaps/warning.md e40806b444aad94fdbda07da07941706 *tests/testthat/helper-lifecycle.R cc60c0f29e05a13f963be35a83dcb2f2 *tests/testthat/output/test-signal-message-args.txt dd3a550a1a1ae8e790e2c8a712514afc *tests/testthat/output/test-signal-message-non-syntactic.txt 8324e8292673d77846bb87082ae3a111 *tests/testthat/output/test-signal-message.txt cde836fec3d05f6f2f938922007cf0d0 *tests/testthat/output/test-warning-backtrace.txt a83475d23ff04ead733e494fa989bff3 *tests/testthat/test-arg.R 6af7226e8c676e77e9b214d5b74e74e3 *tests/testthat/test-badge.R 4031976dc5f5b60e7680fcb0468933aa *tests/testthat/test-deprecated.R 2256bcc2dea2170eea17124dd8cc2b1a *tests/testthat/test-expect.R 2519aeeafd081af5f223d5018ebd240f *tests/testthat/test-lifecycle.R 0c14c92798f14e4d3fbca1149ddda59e *tests/testthat/test-signal.R 5dd5abad164a3420c8573b0a7bba3bdb *tests/testthat/test-spec.R 28180e688a1afccf737520a3fe123710 *tests/testthat/test-verbosity.R f3ce8a7cfe45b851fbf5c66682d4fec0 *tests/testthat/test-warning.R 513c7a7adacddad5d31a17c424a2fbaa *vignettes/communicate.Rmd d69e6099d540152a9c6f9ffde982c0e4 *vignettes/figures/example-badge.png cb1e46f469cfbbbde29c8b5113e1d789 *vignettes/figures/lifecycle-archived.svg c0d2e5a54f1fa4ff02bf9533079dd1f7 *vignettes/figures/lifecycle-defunct.svg a1b8c987c676c16af790f563f96cbb1f *vignettes/figures/lifecycle-deprecated.svg c3978703d8f40f2679795335715e98f4 *vignettes/figures/lifecycle-experimental.svg 952b59dc07b171b97d5d982924244f61 *vignettes/figures/lifecycle-maturing.svg 27b879bf3677ea76e3991d56ab324081 *vignettes/figures/lifecycle-questioning.svg 46de21252239c5a23d400eae83ec6b2d *vignettes/figures/lifecycle-retired.svg 6902bbfaf963fbc4ed98b86bda80caa2 *vignettes/figures/lifecycle-soft-deprecated.svg 53b3f893324260b737b3c46ed2a0e643 *vignettes/figures/lifecycle-stable.svg 4eb49b799369585aa7cb39bbbee022cb *vignettes/figures/lifecycle-superseded.svg c5d20efa2f55c8c06ea5956a30337727 *vignettes/figures/lifecycle.svg bc44bfe9fcf7942b77102ee987278c51 *vignettes/lifecycle.graffle e68b85456ee23fe4f9275a01efb4dc0e *vignettes/lifecycle.html f9cab395a969e8dc9f4384ed7da86390 *vignettes/manage.Rmd 624be490da0e5b2df7cd0de381f06370 *vignettes/stages.Rmd lifecycle/inst/0000755000176200001440000000000014123362332013165 5ustar liggesuserslifecycle/inst/doc/0000755000176200001440000000000014123362332013732 5ustar liggesuserslifecycle/inst/doc/stages.R0000644000176200001440000000056714123362332015353 0ustar liggesusers## ---- eval = FALSE------------------------------------------------------------ # df <- tibble::data_frame(x = 1) # #> Warning message: # #> `data_frame()` is deprecated as of tibble 1.1.0. # #> Please use `tibble()` instead. # #> This warning is displayed once every 8 hours. # #> Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated. lifecycle/inst/doc/communicate.html0000644000176200001440000013530314123362331017130 0ustar liggesusers Communicate lifecycle changes in your functions

Communicate lifecycle changes in your functions

lifecycle provides a standard way to document the lifecycle stages of functions and arguments, paired with tools to steer users away from deprecated functions. Before we go in to the details, make sure that you’re familiar with the lifecycle stages as described in vignette("stages").

Basics

lifecycle badges make it easy for users to see the lifecycle stage when reading the documentation. To use the badges, first call usethis::use_lifecycle() to embed the badge images in your package (you only need to do this once), then use lifecycle::badge() to insert a badge:

#' `r lifecycle::badge("experimental")`
#' `r lifecycle::badge("deprecated")`
#' `r lifecycle::badge("superseded")`

Deprecated functions also need to advertise their status when run. lifecycle provides deprecate_warn() which takes three main arguments:

  • The first argument, when, gives the version number when the deprecation occurred.

  • The second argument, what, describes exactly what was deprecated.

  • The third argument, with, provides the recommended alternative.

We’ll cover the details shortly, but here are a few sample uses:

lifecycle::deprecate_warn("1.0.0", "old_fun()", "new_fun()")
#> Warning: `old_fun()` was deprecated in lifecycle 1.0.0.
#> Please use `new_fun()` instead.
lifecycle::deprecate_warn("1.0.0", "fun()", "testthat::fun()")
#> Warning: `fun()` was deprecated in lifecycle 1.0.0.
#> Please use `testthat::fun()` instead.
lifecycle::deprecate_warn("1.0.0", "fun(old_arg)", "fun(new_arg)")
#> Warning: The `old_arg` argument of `fun()` is deprecated as of lifecycle 1.0.0.
#> Please use the `new_arg` argument instead.

(Note that the message includes the package name — this is automatically discovered from the environment of the calling function so will not work unless the function is called from the package namespace.)

The following sections describe how to use lifecycle badges and functions together to handle a variety of common development tasks.

Functions

Deprecate a function

First, add a badge to the the @description block1. Briefly describe why the deprecation occurred and what to use instead.

#' Add two numbers
#' 
#' @description
#' `r lifecycle::badge("deprecated")`
#' 
#' This function was deprecated because we realised that it's
#' a special case of the [sum()] function.

Next, update the examples to show how to convert from the old usage to the new usage:

#' @examples 
#' add_two(1, 2)
#' # ->
#' sum(1, 2)

Then add @keywords internal to remove the function from the documentation index. If you use pkgdown, also check that it’s no longer listed in _pkgdown.yml. These changes reduce the chance of new users coming across a deprecated function, but don’t prevent those who already know about it from referring to the docs.

#' @keywords internal

You’re now done with the docs, and it’s time to add a warning when the user calls your function. Do this by adding call to deprecate_warn() on the first line of the function:

add_two <- function(x, y) {
  lifecycle::deprecate_warn("1.0.0", "add_two()", "base::sum()")
  x + y
}

add_two(1, 2)
#> Warning: `add_two()` was deprecated in lifecycle 1.0.0.
#> Please use `base::sum()` instead.
#> [1] 3

deprecate_warn() generates user friendly messages for two common deprecation alternatives:

  • Function in same package: lifecycle::deprecate_warn("1.0.0", "fun_old()", "fun_new()")

  • Function in another package: lifecycle::deprecate_warn("1.0.0", "old()", "package::new()")

For other cases, use the details argument to provide your own message to the user:

add_two <- function(x, y) {
  lifecycle::deprecate_warn(
    "1.0.0", 
    "add_two()", 
    details = "This function is a special case of sum(); use it instead."
  )
  x + y
}

add_two(1, 2)
#> Warning: `add_two()` was deprecated in lifecycle 1.0.0.
#> This function is a special case of sum(); use it instead.
#> [1] 3

It’s good practice to test that you’ve correctly implemented the deprecation, testing that the deprecated function still works and that it generates a useful warning. Using an expectation inside testthat::expect_snapshot()2 is a convenient way to do this:

test_that("add_two is deprecated", {
  expect_snapshot({
    x <- add_two(1, 1)
    expect_equal(x, 2)
  })
})

If you have existing tests for the deprecated function you can suppress the warning in those tests with the lifecycle_verbosity option:

test_that("add_two returns the sum of its inputs", {
  withr::local_options(lifecycle_verbosity = "quiet")
  expect_equal(add_two(1, 1), 2)
})

And then add a separate test specifically for the deprecation.

test_that("add_two is deprecated", {
  expect_snapshot(add_two(1, 1))
})

Gradual deprecation

For particularly important functions, you can choose to add two other stages to the deprecation process:

  • deprecate_soft() is used before deprecate_warn(). This function only warns (a) users who try the feature from the global environment and (b) developers who directly use the feature (when running testthat tests). There is no warning when the deprecated feature is called indirectly by another package — the goal is to ensure that warn only the person who has the power to stop using the deprecated feature.

  • deprecate_stop() comes after deprecate_warn() and generates an error instead of a warning. The main benefit over simply removing the function is that the user is informed about the replacement.

If you use these stages you’ll also need a process for bumping the deprecation stage for major and minor releases. We recommend something like this:

  1. Search for deprecate_stop() and consider if you’re ready to the remove the function completely.

  2. Search for deprecate_warn() and replace with deprecate_stop(). Remove the remaining body of the function and any tests.

  3. Search for deprecate_soft() and replace with deprecate_warn().

Rename a function

To rename a function without breaking existing code, move the implementation to the new function, then call the new function from the old function, along with a deprecation message:

#' Add two numbers
#' 
#' @description 
#' `r lifecycle::badge("deprecated")`
#' 
#' `add_two()` was renamed to `number_add()` to create a more
#' consistent API.
#' @keywords internal
#' @export
add_two <- function(foo, bar) {
  lifecycle::deprecate_warn("1.0.0", "add_two()", "number_add()")
  number_add(foo, bar)
}

# documentation goes here...
#' @export
number_add <- function(x, y) {
  x + y
}

If you are renaming many functions as part of an API overhaul, it’ll often make sense to document all the changes in one file, like https://rvest.tidyverse.org/reference/rename.html.

Supersede a function

Superseding a function is simpler than deprecating it, since you don’t need to steer users away from it with a warning. So all you need to do is add a superseded badge:

#' Gather columns into key-value pairs
#'
#' @description
#' `r lifecycle::badge("superseded")`

Then describe why the function was superseded, and what the recommended alternative is:

#'
#' Development on `gather()` is complete, and for new code we recommend
#' switching to `pivot_longer()`, which is easier to use, more featureful,
#' and still under active development.
#' 
#' In brief,
#' `df %>% gather("key", "value", x, y, z)` is equivalent to
#' `df %>% pivot_longer(c(x, y, z), names_to = "key", values_to = "value")`.
#' See more details in `vignette("pivot")`.

The rest of the documentation can stay the same.

If you’re willing to live on the bleeding edge of lifecycle, add a call to the experimental signal_stage():

gather <- function(data, key = "key", value = "value", ...) {
  lifecycle::signal_stage("superseded", "gather()")
}

This signal isn’t currently hooked up to any behaviour, but we plan to provide logging and analysis tools in a future release.

Mark function as experimental

To advertise that a function is experimental and the interface might change in the future, first add an experimental badge to the description:

#' @description
#' `r lifecycle::badge("experimental")`

If the function is very experimental, you might want to add @keywords internal too.

If you’re willing to try an experimental lifecycle feature, add a call to signal_stage() in the body:

cool_function <- function() {
  lifecycle::signal_stage("experimental", "cool_function()")
}

This signal isn’t currently hooked up to any behaviour, but we plan to provide logging and analysis tools in a future release.

Arguments

Deprecate an argument, keeping the existing default

Take this example where we want to deprecate na.rm in favour of always making it TRUE.

add_two <- function(x, y, na.rm = TRUE) {
  sum(x, y, na.rm = na.rm)
}

First, add a badge to the argument description:

#' @param na.rm `r lifecycle::badge("deprecated")` `na.rm = FALSE` is no
#'   longer supported; this function will always remove missing values

And add a deprecation warning if na.rm is FALSE. In this case, there’s no replacement to the behaviour, so we instead use details to provide a custom message:

add_two <- function(x, y, na.rm = TRUE) {
  if (!isTRUE(na.rm)) {
    lifecycle::deprecate_warn(
      when = "1.0.0", 
      what = "add_two(na.rm)",
      details = "Ability to retain missing values will be dropped in next release."
    )
  }
  
  sum(x, y, na.rm = na.rm)
}

add_two(1, NA, na.rm = TRUE)
#> [1] 1
add_two(1, NA, na.rm = FALSE)
#> Warning: The `na.rm` argument of `add_two()` is deprecated as of lifecycle 1.0.0.
#> Ability to retain missing values will be dropped in next release.
#> [1] NA

Deprecating an argument, providing a new default

Alternatively, you can change the default value to lifecycle::deprecated() to make the deprecation status more obvious from the outside, and use lifecycle::is_present() to test whether or not the argument was provided. Unlike missing(), this works for both direct and indirect calls.

#' @importFrom lifecycle deprecated
add_two <- function(x, y, na.rm = deprecated()) {
  if (lifecycle::is_present(na.rm)) {
    lifecycle::deprecate_warn(
      when = "1.0.0", 
      what = "add_two(na.rm)",
      details = "Ability to retain missing values will be dropped in next release."
    )
  }
  
  sum(x, y, na.rm = na.rm)
}

The chief advantage of this technique is that users will get a warning regardless of what value of na.rm they use:

add_two(1, NA, na.rm = TRUE)
#> Warning: The `na.rm` argument of `add_two()` is deprecated as of lifecycle 1.0.0.
#> Ability to retain missing values will be dropped in next release.
#> [1] 1
add_two(1, NA, na.rm = FALSE)
#> Warning: The `na.rm` argument of `add_two()` is deprecated as of lifecycle 1.0.0.
#> Ability to retain missing values will be dropped in next release.
#> [1] NA

Renaming an argument

You may want to rename an argument if you realise you have made a mistake with the name of an argument. For example, you’ve realised that an argument accidentally uses . to separate a compound name, instead of _. You’ll need to temporarily permit both arguments, generating a deprecation warning when the user supplies the old argument:

add_two <- function(x, y, na_rm = TRUE, na.rm = deprecated()) {
  if (lifecycle::is_present(na.rm)) {
    lifecycle::deprecate_warn("1.0.0", "add_two(na.rm)", "add_two(na_rm)")
    na_rm <- na.rm
  }
  
  add_two(x, y, na.rm = na_rm)
}

Reducing allowed inputs to an argument

To narrow the set of allowed inputs, call deprecate_warn() only when the user supplies the previously supported inputs. Make sure you preserve the previous behaviour:

add_two <- function(x, y) {
  if (length(y) != 1) {
    lifecycle::deprecate_warn("1.0.0", "foo(y = 'must be a scalar')")
    y <- sum(y)
  }
  x + y
}

add_two(1, 2)
#> [1] 3
add_two(1, 1:5)
#> Warning: The `y` argument of `foo()` must be a scalar as of lifecycle 1.0.0.
#> [1] 16

  1. We only use an explicit @description when the description will be multiple paragraphs, as in these examples.↩︎

  2. You can learn more about snapshot testing in vignette("snapshotting", package = "testthat").↩︎

lifecycle/inst/doc/communicate.R0000644000176200001440000001355014123362331016364 0ustar liggesusers## ---- include = FALSE--------------------------------------------------------- knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) options( # Pretend we're in the lifecycle package "lifecycle:::calling_package" = "lifecycle", # suppress last_lifecycle_warnings() message by default "lifecycle_verbosity" = "warning" ) ## ---- eval = FALSE------------------------------------------------------------ # #' `r lifecycle::badge("experimental")` # #' `r lifecycle::badge("deprecated")` # #' `r lifecycle::badge("superseded")` ## ----------------------------------------------------------------------------- lifecycle::deprecate_warn("1.0.0", "old_fun()", "new_fun()") lifecycle::deprecate_warn("1.0.0", "fun()", "testthat::fun()") lifecycle::deprecate_warn("1.0.0", "fun(old_arg)", "fun(new_arg)") ## ----------------------------------------------------------------------------- #' Add two numbers #' #' @description #' `r lifecycle::badge("deprecated")` #' #' This function was deprecated because we realised that it's #' a special case of the [sum()] function. ## ----------------------------------------------------------------------------- #' @examples #' add_two(1, 2) #' # -> #' sum(1, 2) ## ----------------------------------------------------------------------------- #' @keywords internal ## ----------------------------------------------------------------------------- add_two <- function(x, y) { lifecycle::deprecate_warn("1.0.0", "add_two()", "base::sum()") x + y } add_two(1, 2) ## ----------------------------------------------------------------------------- add_two <- function(x, y) { lifecycle::deprecate_warn( "1.0.0", "add_two()", details = "This function is a special case of sum(); use it instead." ) x + y } add_two(1, 2) ## ---- eval = FALSE------------------------------------------------------------ # test_that("add_two is deprecated", { # expect_snapshot({ # x <- add_two(1, 1) # expect_equal(x, 2) # }) # }) ## ---- eval = FALSE------------------------------------------------------------ # test_that("add_two returns the sum of its inputs", { # withr::local_options(lifecycle_verbosity = "quiet") # expect_equal(add_two(1, 1), 2) # }) ## ---- eval = FALSE------------------------------------------------------------ # test_that("add_two is deprecated", { # expect_snapshot(add_two(1, 1)) # }) ## ----------------------------------------------------------------------------- #' Add two numbers #' #' @description #' `r lifecycle::badge("deprecated")` #' #' `add_two()` was renamed to `number_add()` to create a more #' consistent API. #' @keywords internal #' @export add_two <- function(foo, bar) { lifecycle::deprecate_warn("1.0.0", "add_two()", "number_add()") number_add(foo, bar) } # documentation goes here... #' @export number_add <- function(x, y) { x + y } ## ----------------------------------------------------------------------------- #' Gather columns into key-value pairs #' #' @description #' `r lifecycle::badge("superseded")` ## ----------------------------------------------------------------------------- #' #' Development on `gather()` is complete, and for new code we recommend #' switching to `pivot_longer()`, which is easier to use, more featureful, #' and still under active development. #' #' In brief, #' `df %>% gather("key", "value", x, y, z)` is equivalent to #' `df %>% pivot_longer(c(x, y, z), names_to = "key", values_to = "value")`. #' See more details in `vignette("pivot")`. ## ----------------------------------------------------------------------------- gather <- function(data, key = "key", value = "value", ...) { lifecycle::signal_stage("superseded", "gather()") } ## ----------------------------------------------------------------------------- #' @description #' `r lifecycle::badge("experimental")` ## ----------------------------------------------------------------------------- cool_function <- function() { lifecycle::signal_stage("experimental", "cool_function()") } ## ----------------------------------------------------------------------------- add_two <- function(x, y, na.rm = TRUE) { sum(x, y, na.rm = na.rm) } ## ----------------------------------------------------------------------------- #' @param na.rm `r lifecycle::badge("deprecated")` `na.rm = FALSE` is no #' longer supported; this function will always remove missing values ## ----------------------------------------------------------------------------- add_two <- function(x, y, na.rm = TRUE) { if (!isTRUE(na.rm)) { lifecycle::deprecate_warn( when = "1.0.0", what = "add_two(na.rm)", details = "Ability to retain missing values will be dropped in next release." ) } sum(x, y, na.rm = na.rm) } add_two(1, NA, na.rm = TRUE) add_two(1, NA, na.rm = FALSE) ## ----------------------------------------------------------------------------- #' @importFrom lifecycle deprecated add_two <- function(x, y, na.rm = deprecated()) { if (lifecycle::is_present(na.rm)) { lifecycle::deprecate_warn( when = "1.0.0", what = "add_two(na.rm)", details = "Ability to retain missing values will be dropped in next release." ) } sum(x, y, na.rm = na.rm) } ## ----------------------------------------------------------------------------- add_two(1, NA, na.rm = TRUE) add_two(1, NA, na.rm = FALSE) ## ----------------------------------------------------------------------------- add_two <- function(x, y, na_rm = TRUE, na.rm = deprecated()) { if (lifecycle::is_present(na.rm)) { lifecycle::deprecate_warn("1.0.0", "add_two(na.rm)", "add_two(na_rm)") na_rm <- na.rm } add_two(x, y, na.rm = na_rm) } ## ----------------------------------------------------------------------------- add_two <- function(x, y) { if (length(y) != 1) { lifecycle::deprecate_warn("1.0.0", "foo(y = 'must be a scalar')") y <- sum(y) } x + y } add_two(1, 2) add_two(1, 1:5) lifecycle/inst/doc/stages.Rmd0000644000176200001440000001617214123356455015704 0ustar liggesusers--- title: "Lifecycle stages" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Lifecycle stages} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- This vignette describes the four primary stages of the tidyverse lifecycle: stable, deprecated, superseded, and experimental. ![A diagram showing the transitions between the four main stages: experimental can become stable and stable can become deprecated or superseded.](figures/lifecycle.svg){width="75%"} The lifecycle stages can apply to packages, functions, function arguments, and even specific values of a function argument. However, you'll mostly see them used to label individual functions, so that's the language we use below. ## Stable The default development stage is ![stable](figures/lifecycle-stable.svg){style="vertical-align:middle"}. A function is considered stable when the author is happy with its interface, doesn't see major issues, and is happy to share it with the world. Because this stage is the default, functions will only be given a stable badge if there's some specific need to draw attention to their status. Stability is defined in terms of breaking changes. A **breaking change** is a change that breaks code that uses the function as expected. In general, breaking changes reduce the set of code that works without error, either by removing a function, removing a function argument, or decreasing the set of valid inputs. Breaking changes also include changes to output type. If you imagine all the possible inputs to a function that return a result (and not an error), a breaking change makes the set smaller. Not all changes that cause your function to stop working are breaking changes. For example, you might have accidentally relied on a bug. When the bug is fixed, your code breaks, but this is not a breaking change. A good way of making your code more robust to this sort of behaviour change is to only use a function for its explicitly intended effects. For example, using `c()` to concatenate two vectors will not put your code at risk because it clearly is the intended usage of this function. On the other hand, using `c()` just for the side effect of removing attributes is probably not a good idea. If you use `c(factor("foo"))` to retrieve the underlying integers of the factor levels, your code is at risk of breaking if `c()` ever implements a factor method. Stable functions come with two promises related to breaking changes: - Breaking changes will be avoided where possible. We'll only make breaking changes if we consider the long term benefit of the change to be greater than short term pain of changing existing code. - If a breaking change is needed, it will occur gradually, through the deprecation process described next. This gives you plenty of time to adjust your code before it starts generating errors. ## Deprecated A ![Deprecated](figures/lifecycle-deprecated.svg){style="vertical-align:middle"} function has a better alternative available and is scheduled for removal. When you call a deprecated function, you get a warning telling you what to use instead. For example, take `tibble::as_data_frame()`: ```{r, eval = FALSE} df <- tibble::data_frame(x = 1) #> Warning message: #> `data_frame()` is deprecated as of tibble 1.1.0. #> Please use `tibble()` instead. #> This warning is displayed once every 8 hours. #> Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated. ``` The deprecation warning tells you when the function was deprecated (in tibble 1.1.0, released in 2016), and what to use instead (`tibble()`). To avoid being too annoying, deprecation messages will only appear once per session, and you can find out exactly where they come from by calling `lifecycle::last_lifecycle_warnings()`. `vignette("manage")` provides more advice on handling deprecation warnings in your code. Particularly important functions may go through two additional stages of deprecation: - **Soft deprecated** comes before deprecated. It's a gentler form of deprecation designed to prevent new uses of a function and encourage package developers to move away from it. Soft deprecated allows a package to change its interface to encourage package developers to update their code before their users are forced to change. - **Defunct** comes after deprecated. In most cases, a deprecated function will eventually just be deleted. For very important functions, we'll instead make the function defunct, which means that function continues to exist but the deprecation warning turns into an error. This is more user-friendly than just removing the function because users will get a clear error message explaining why their code no longer works and how they can fix it. ## Superseded A softer alternative to deprecation is superseded. A ![superseded](figures/lifecycle-superseded.svg)[^1] function has a known better alternative, but the function itself is not going away . A superseded function will not emit a warning (since there's no risk if you keep using it), but the documentation will tell you what we recommend instead . [^1]: This stage was previously called retired. Superseded functions will not receive new features, but will receive any critical bug fixes needed to keep it working. In some ways a superseded function is actually safer than a stable function because it's guaranteed never to change (for better or for worse). ## Experimental Some functions are released in an ![experimental](figures/lifecycle-experimental.svg){style="vertical-align:middle"} stage. Experimental functions are made available so people can try them out and provide feedback, but come with no promises for long term stability. In particular, the author reserves the right to make breaking changes without a deprecation cycle. That said, there is some interaction between popularity and stability. Breaking an popular function, even if clearly labelled as experimental, is likely to cause widespread pain so we'll generally try to avoid it. In general, you can assume any package with version number less than 1.0.0 is at least somewhat experimental, and it may have major changes in its future. The most experimental packages only exist on GitHub. If you're using a non-CRAN package you should plan for an active relationship: when the package changes, you need to be prepared to update your code. ## Superseded stages We no longer use these stages, but we document them here because we have used them in the past. ### Questioning Sometimes the author of a function is no longer certain that a function is the optimal approach, but doesn't yet know how to do it better. These functions can be marked as ![questioning](figures/lifecycle-questioning.svg){style="vertical-align:middle"} to give users a heads up that the author has doubts about the function. Because knowing that a function is questioning is not very actionable, we no longer use or recommend this stage. ### Maturing Previously we used as ![maturing](figures/lifecycle-maturing.svg){style="vertical-align:middle"} for functions that lay somewhere between experimental and stable. We stopped using this stage because, like questioning, it's not clear what actionable information this stage delivers. lifecycle/inst/doc/communicate.Rmd0000644000176200001440000002712714123354433016716 0ustar liggesusers--- title: "Communicate lifecycle changes in your functions" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Communicate lifecycle changes in your functions} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) options( # Pretend we're in the lifecycle package "lifecycle:::calling_package" = "lifecycle", # suppress last_lifecycle_warnings() message by default "lifecycle_verbosity" = "warning" ) ``` lifecycle provides a standard way to document the lifecycle stages of functions and arguments, paired with tools to steer users away from deprecated functions. Before we go in to the details, make sure that you're familiar with the lifecycle stages as described in `vignette("stages")`. ## Basics lifecycle badges make it easy for users to see the lifecycle stage when reading the documentation. To use the badges, first call `usethis::use_lifecycle()` to embed the badge images in your package (you only need to do this once), then use `lifecycle::badge()` to insert a badge: ```{r, eval = FALSE} #' `r lifecycle::badge("experimental")` #' `r lifecycle::badge("deprecated")` #' `r lifecycle::badge("superseded")` ``` Deprecated functions also need to advertise their status when run. lifecycle provides `deprecate_warn()` which takes three main arguments: - The first argument, `when`, gives the version number when the deprecation occurred. - The second argument, `what`, describes exactly what was deprecated. - The third argument, `with`, provides the recommended alternative. We'll cover the details shortly, but here are a few sample uses: ```{r} lifecycle::deprecate_warn("1.0.0", "old_fun()", "new_fun()") lifecycle::deprecate_warn("1.0.0", "fun()", "testthat::fun()") lifecycle::deprecate_warn("1.0.0", "fun(old_arg)", "fun(new_arg)") ``` (Note that the message includes the package name --- this is automatically discovered from the environment of the calling function so will not work unless the function is called from the package namespace.) The following sections describe how to use lifecycle badges and functions together to handle a variety of common development tasks. ## Functions ### Deprecate a function First, add a badge to the the `@description` block[^1]. Briefly describe why the deprecation occurred and what to use instead. [^1]: We only use an explicit `@description` when the description will be multiple paragraphs, as in these examples. ```{r} #' Add two numbers #' #' @description #' `r lifecycle::badge("deprecated")` #' #' This function was deprecated because we realised that it's #' a special case of the [sum()] function. ``` Next, update the examples to show how to convert from the old usage to the new usage: ```{r} #' @examples #' add_two(1, 2) #' # -> #' sum(1, 2) ``` Then add `@keywords internal` to remove the function from the documentation index. If you use pkgdown, also check that it's no longer listed in `_pkgdown.yml`. These changes reduce the chance of new users coming across a deprecated function, but don't prevent those who already know about it from referring to the docs. ```{r} #' @keywords internal ``` You're now done with the docs, and it's time to add a warning when the user calls your function. Do this by adding call to `deprecate_warn()` on the first line of the function: ```{r} add_two <- function(x, y) { lifecycle::deprecate_warn("1.0.0", "add_two()", "base::sum()") x + y } add_two(1, 2) ``` `deprecate_warn()` generates user friendly messages for two common deprecation alternatives: - Function in same package: `lifecycle::deprecate_warn("1.0.0", "fun_old()", "fun_new()")` - Function in another package: `lifecycle::deprecate_warn("1.0.0", "old()", "package::new()")` For other cases, use the `details` argument to provide your own message to the user: ```{r} add_two <- function(x, y) { lifecycle::deprecate_warn( "1.0.0", "add_two()", details = "This function is a special case of sum(); use it instead." ) x + y } add_two(1, 2) ``` It's good practice to test that you've correctly implemented the deprecation, testing that the deprecated function still works and that it generates a useful warning. Using an expectation inside `testthat::expect_snapshot()`[^2] is a convenient way to do this: [^2]: You can learn more about snapshot testing in `vignette("snapshotting", package = "testthat")`. ```{r, eval = FALSE} test_that("add_two is deprecated", { expect_snapshot({ x <- add_two(1, 1) expect_equal(x, 2) }) }) ``` If you have existing tests for the deprecated function you can suppress the warning in those tests with the `lifecycle_verbosity` option: ```{r, eval = FALSE } test_that("add_two returns the sum of its inputs", { withr::local_options(lifecycle_verbosity = "quiet") expect_equal(add_two(1, 1), 2) }) ``` And then add a separate test specifically for the deprecation. ```{r, eval = FALSE} test_that("add_two is deprecated", { expect_snapshot(add_two(1, 1)) }) ``` ### Gradual deprecation For particularly important functions, you can choose to add two other stages to the deprecation process: - `deprecate_soft()` is used before `deprecate_warn()`. This function only warns (a) users who try the feature from the global environment and (b) developers who directly use the feature (when running testthat tests). There is no warning when the deprecated feature is called indirectly by another package --- the goal is to ensure that warn only the person who has the power to stop using the deprecated feature. - `deprecate_stop()` comes after `deprecate_warn()` and generates an error instead of a warning. The main benefit over simply removing the function is that the user is informed about the replacement. If you use these stages you'll also need a process for bumping the deprecation stage for major and minor releases. We recommend something like this: 1. Search for `deprecate_stop()` and consider if you're ready to the remove the function completely. 2. Search for `deprecate_warn()` and replace with `deprecate_stop()`. Remove the remaining body of the function and any tests. 3. Search for `deprecate_soft()` and replace with `deprecate_warn()`. ### Rename a function To rename a function without breaking existing code, move the implementation to the new function, then call the new function from the old function, along with a deprecation message: ```{r} #' Add two numbers #' #' @description #' `r lifecycle::badge("deprecated")` #' #' `add_two()` was renamed to `number_add()` to create a more #' consistent API. #' @keywords internal #' @export add_two <- function(foo, bar) { lifecycle::deprecate_warn("1.0.0", "add_two()", "number_add()") number_add(foo, bar) } # documentation goes here... #' @export number_add <- function(x, y) { x + y } ``` If you are renaming many functions as part of an API overhaul, it'll often make sense to document all the changes in one file, like . ### Supersede a function Superseding a function is simpler than deprecating it, since you don't need to steer users away from it with a warning. So all you need to do is add a superseded badge: ```{r} #' Gather columns into key-value pairs #' #' @description #' `r lifecycle::badge("superseded")` ``` Then describe why the function was superseded, and what the recommended alternative is: ```{r} #' #' Development on `gather()` is complete, and for new code we recommend #' switching to `pivot_longer()`, which is easier to use, more featureful, #' and still under active development. #' #' In brief, #' `df %>% gather("key", "value", x, y, z)` is equivalent to #' `df %>% pivot_longer(c(x, y, z), names_to = "key", values_to = "value")`. #' See more details in `vignette("pivot")`. ``` The rest of the documentation can stay the same. If you're willing to live on the bleeding edge of lifecycle, add a call to the experimental `signal_stage()`: ```{r} gather <- function(data, key = "key", value = "value", ...) { lifecycle::signal_stage("superseded", "gather()") } ``` This signal isn't currently hooked up to any behaviour, but we plan to provide logging and analysis tools in a future release. ### Mark function as experimental To advertise that a function is experimental and the interface might change in the future, first add an experimental badge to the description: ```{r} #' @description #' `r lifecycle::badge("experimental")` ``` If the function is very experimental, you might want to add `@keywords internal` too. If you're willing to try an experimental lifecycle feature, add a call to `signal_stage()` in the body: ```{r} cool_function <- function() { lifecycle::signal_stage("experimental", "cool_function()") } ``` This signal isn't currently hooked up to any behaviour, but we plan to provide logging and analysis tools in a future release. ## Arguments ### Deprecate an argument, keeping the existing default Take this example where we want to deprecate `na.rm` in favour of always making it `TRUE.` ```{r} add_two <- function(x, y, na.rm = TRUE) { sum(x, y, na.rm = na.rm) } ``` First, add a badge to the argument description: ```{r} #' @param na.rm `r lifecycle::badge("deprecated")` `na.rm = FALSE` is no #' longer supported; this function will always remove missing values ``` And add a deprecation warning if `na.rm` is FALSE. In this case, there's no replacement to the behaviour, so we instead use `details` to provide a custom message: ```{r} add_two <- function(x, y, na.rm = TRUE) { if (!isTRUE(na.rm)) { lifecycle::deprecate_warn( when = "1.0.0", what = "add_two(na.rm)", details = "Ability to retain missing values will be dropped in next release." ) } sum(x, y, na.rm = na.rm) } add_two(1, NA, na.rm = TRUE) add_two(1, NA, na.rm = FALSE) ``` ### Deprecating an argument, providing a new default Alternatively, you can change the default value to `lifecycle::deprecated()` to make the deprecation status more obvious from the outside, and use `lifecycle::is_present()` to test whether or not the argument was provided. Unlike `missing()`, this works for both direct and indirect calls. ```{r} #' @importFrom lifecycle deprecated add_two <- function(x, y, na.rm = deprecated()) { if (lifecycle::is_present(na.rm)) { lifecycle::deprecate_warn( when = "1.0.0", what = "add_two(na.rm)", details = "Ability to retain missing values will be dropped in next release." ) } sum(x, y, na.rm = na.rm) } ``` The chief advantage of this technique is that users will get a warning regardless of what value of `na.rm` they use: ```{r} add_two(1, NA, na.rm = TRUE) add_two(1, NA, na.rm = FALSE) ``` ### Renaming an argument You may want to rename an argument if you realise you have made a mistake with the name of an argument. For example, you've realised that an argument accidentally uses `.` to separate a compound name, instead of `_`. You'll need to temporarily permit both arguments, generating a deprecation warning when the user supplies the old argument: ```{r} add_two <- function(x, y, na_rm = TRUE, na.rm = deprecated()) { if (lifecycle::is_present(na.rm)) { lifecycle::deprecate_warn("1.0.0", "add_two(na.rm)", "add_two(na_rm)") na_rm <- na.rm } add_two(x, y, na.rm = na_rm) } ``` ### Reducing allowed inputs to an argument To narrow the set of allowed inputs, call `deprecate_warn()` only when the user supplies the previously supported inputs. Make sure you preserve the previous behaviour: ```{r} add_two <- function(x, y) { if (length(y) != 1) { lifecycle::deprecate_warn("1.0.0", "foo(y = 'must be a scalar')") y <- sum(y) } x + y } add_two(1, 2) add_two(1, 1:5) ``` lifecycle/inst/doc/stages.html0000644000176200001440000007301114123362332016110 0ustar liggesusers Lifecycle stages

Lifecycle stages

This vignette describes the four primary stages of the tidyverse lifecycle: stable, deprecated, superseded, and experimental.

A diagram showing the transitions between the four main stages: experimental can become stable and stable can become deprecated or superseded.

The lifecycle stages can apply to packages, functions, function arguments, and even specific values of a function argument. However, you’ll mostly see them used to label individual functions, so that’s the language we use below.

Stable

The default development stage is stable. A function is considered stable when the author is happy with its interface, doesn’t see major issues, and is happy to share it with the world. Because this stage is the default, functions will only be given a stable badge if there’s some specific need to draw attention to their status.

Stability is defined in terms of breaking changes. A breaking change is a change that breaks code that uses the function as expected. In general, breaking changes reduce the set of code that works without error, either by removing a function, removing a function argument, or decreasing the set of valid inputs. Breaking changes also include changes to output type. If you imagine all the possible inputs to a function that return a result (and not an error), a breaking change makes the set smaller.

Not all changes that cause your function to stop working are breaking changes. For example, you might have accidentally relied on a bug. When the bug is fixed, your code breaks, but this is not a breaking change. A good way of making your code more robust to this sort of behaviour change is to only use a function for its explicitly intended effects. For example, using c() to concatenate two vectors will not put your code at risk because it clearly is the intended usage of this function. On the other hand, using c() just for the side effect of removing attributes is probably not a good idea. If you use c(factor("foo")) to retrieve the underlying integers of the factor levels, your code is at risk of breaking if c() ever implements a factor method.

Stable functions come with two promises related to breaking changes:

  • Breaking changes will be avoided where possible. We’ll only make breaking changes if we consider the long term benefit of the change to be greater than short term pain of changing existing code.

  • If a breaking change is needed, it will occur gradually, through the deprecation process described next. This gives you plenty of time to adjust your code before it starts generating errors.

Deprecated

A Deprecated function has a better alternative available and is scheduled for removal. When you call a deprecated function, you get a warning telling you what to use instead. For example, take tibble::as_data_frame():

df <- tibble::data_frame(x = 1)
#> Warning message:
#> `data_frame()` is deprecated as of tibble 1.1.0.
#> Please use `tibble()` instead.
#> This warning is displayed once every 8 hours.
#> Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated. 

The deprecation warning tells you when the function was deprecated (in tibble 1.1.0, released in 2016), and what to use instead (tibble()). To avoid being too annoying, deprecation messages will only appear once per session, and you can find out exactly where they come from by calling lifecycle::last_lifecycle_warnings(). vignette("manage") provides more advice on handling deprecation warnings in your code.

Particularly important functions may go through two additional stages of deprecation:

  • Soft deprecated comes before deprecated. It’s a gentler form of deprecation designed to prevent new uses of a function and encourage package developers to move away from it. Soft deprecated allows a package to change its interface to encourage package developers to update their code before their users are forced to change.

  • Defunct comes after deprecated. In most cases, a deprecated function will eventually just be deleted. For very important functions, we’ll instead make the function defunct, which means that function continues to exist but the deprecation warning turns into an error. This is more user-friendly than just removing the function because users will get a clear error message explaining why their code no longer works and how they can fix it.

Superseded

A softer alternative to deprecation is superseded. A superseded1 function has a known better alternative, but the function itself is not going away . A superseded function will not emit a warning (since there’s no risk if you keep using it), but the documentation will tell you what we recommend instead .

Superseded functions will not receive new features, but will receive any critical bug fixes needed to keep it working. In some ways a superseded function is actually safer than a stable function because it’s guaranteed never to change (for better or for worse).

Experimental

Some functions are released in an experimental stage. Experimental functions are made available so people can try them out and provide feedback, but come with no promises for long term stability. In particular, the author reserves the right to make breaking changes without a deprecation cycle. That said, there is some interaction between popularity and stability. Breaking an popular function, even if clearly labelled as experimental, is likely to cause widespread pain so we’ll generally try to avoid it.

In general, you can assume any package with version number less than 1.0.0 is at least somewhat experimental, and it may have major changes in its future. The most experimental packages only exist on GitHub. If you’re using a non-CRAN package you should plan for an active relationship: when the package changes, you need to be prepared to update your code.

Superseded stages

We no longer use these stages, but we document them here because we have used them in the past.

Questioning

Sometimes the author of a function is no longer certain that a function is the optimal approach, but doesn’t yet know how to do it better. These functions can be marked as questioning to give users a heads up that the author has doubts about the function. Because knowing that a function is questioning is not very actionable, we no longer use or recommend this stage.

Maturing

Previously we used as maturing for functions that lay somewhere between experimental and stable. We stopped using this stage because, like questioning, it’s not clear what actionable information this stage delivers.


  1. This stage was previously called retired.↩︎

lifecycle/inst/doc/manage.R0000644000176200001440000000250614123362331015307 0ustar liggesusers## ---- include = FALSE--------------------------------------------------------- knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) options("lifecycle:::calling_package" = "tibble") ## ----------------------------------------------------------------------------- data_frame <- function(...) { lifecycle::deprecate_warn("1.1.0", "data_frame()", "tibble()") tibble::tibble(...) } ## ----------------------------------------------------------------------------- df1 <- data_frame(x = 1, y = 2) #> This warning is displayed once every 8 hours. #> Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated. df2 <- data_frame(a = "apple", b = "banana") ## ---- eval = FALSE------------------------------------------------------------ # lifecycle::last_lifecycle_warnings() # #> [[1]] # #> # #> message: `data_frame()` was deprecated in tibble 1.1.0. # #> Please use `tibble()` instead. # #> Backtrace: # #> 1. global::data_frame(x = 1) ## ----------------------------------------------------------------------------- options(lifecycle_verbosity = "warning") df1 <- data_frame(x = 1, y = 2) df2 <- data_frame(a = "apple", b = "banana") ## ---- error = TRUE------------------------------------------------------------ options("lifecycle_verbosity" = "error") df1 <- data_frame(x = 1, y = 2) lifecycle/inst/doc/manage.html0000644000176200001440000004403014123362331016050 0ustar liggesusers Manage lifecycle changes in functions you use

Manage lifecycle changes in functions you use

The lifecycle package uses warnings to tell you about deprecated functions. Deprecated functions will be removed in a future release, so it’s good practice to eliminate the warnings as soon as you see them.

For example, lets imagine your code uses tibble::data_frame(), which was deprecated in favour of tibble() in version 1.1.0. data_frame() now looks something like this:

data_frame <- function(...) {
  lifecycle::deprecate_warn("1.1.0", "data_frame()", "tibble()")
  tibble::tibble(...)
}

That means if you use data_frame() in your own code you’ll get a warning:

df1 <- data_frame(x = 1, y = 2)
#> Warning: `data_frame()` was deprecated in tibble 1.1.0.
#> Please use `tibble()` instead.
#> This warning is displayed once every 8 hours.
#> Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.
df2 <- data_frame(a = "apple", b = "banana")
#> Warning: `data_frame()` was deprecated in tibble 1.1.0.
#> Please use `tibble()` instead.

You’ll notice that the warning only appears the first time we call it — lifecycle only notifies you every 8 hours so it’s not overly disruptive if you’ve used a deprecated function in many places.

So how do you track down exactly where the warning came from? Firstly, you might notice the deprecation warning message includes the advice to call lifecycle::last_lifecycle_warnings(). That’ll give you a list of all the deprecation warnings that have happened recently:

lifecycle::last_lifecycle_warnings()
#> [[1]]
#> <deprecated>
#> message: `data_frame()` was deprecated in tibble 1.1.0.
#> Please use `tibble()` instead.
#> Backtrace:
#>  1. global::data_frame(x = 1)

Each warning comes with a back trace that shows you the full sequence of calls that lead to the deprecated function.

Alternatively, if you’re ready to spend some time tracking down all your uses of deprecated functions, you can use the lifecycle_verbosity option to make deprecated functions warn every time:

options(lifecycle_verbosity = "warning")
df1 <- data_frame(x = 1, y = 2)
#> Warning: `data_frame()` was deprecated in tibble 1.1.0.
#> Please use `tibble()` instead.
df2 <- data_frame(a = "apple", b = "banana")
#> Warning: `data_frame()` was deprecated in tibble 1.1.0.
#> Please use `tibble()` instead.

Then use lifecycle::last_lifecycle_warnings() to track down the source.

Alternatively, if you want to be really strict, you can turn all deprecation warnings into errors, forcing you to deal with them immediately:

options("lifecycle_verbosity" = "error")
df1 <- data_frame(x = 1, y = 2)
#> Error: `data_frame()` was deprecated in tibble 1.1.0.
#> Please use `tibble()` instead.
lifecycle/inst/doc/manage.Rmd0000644000176200001440000000514614123356455015645 0ustar liggesusers--- title: "Manage lifecycle changes in functions you use" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Manage lifecycle changes in functions you use} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) options("lifecycle:::calling_package" = "tibble") ``` The lifecycle package uses warnings to tell you about deprecated functions. Deprecated functions will be removed in a future release, so it's good practice to eliminate the warnings as soon as you see them. For example, lets imagine your code uses `tibble::data_frame()`, which was deprecated in favour of `tibble()` in version 1.1.0. `data_frame()` now looks something like this: ```{r} data_frame <- function(...) { lifecycle::deprecate_warn("1.1.0", "data_frame()", "tibble()") tibble::tibble(...) } ``` That means if you use `data_frame()` in your own code you'll get a warning: ```{r} df1 <- data_frame(x = 1, y = 2) #> This warning is displayed once every 8 hours. #> Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated. df2 <- data_frame(a = "apple", b = "banana") ``` You'll notice that the warning only appears the first time we call it --- lifecycle only notifies you every 8 hours so it's not overly disruptive if you've used a deprecated function in many places. So how do you track down exactly where the warning came from? Firstly, you might notice the deprecation warning message includes the advice to call `lifecycle::last_lifecycle_warnings()`. That'll give you a list of all the deprecation warnings that have happened recently: ```{r, eval = FALSE} lifecycle::last_lifecycle_warnings() #> [[1]] #> #> message: `data_frame()` was deprecated in tibble 1.1.0. #> Please use `tibble()` instead. #> Backtrace: #> 1. global::data_frame(x = 1) ``` Each warning comes with a back trace that shows you the full sequence of calls that lead to the deprecated function. Alternatively, if you're ready to spend some time tracking down all your uses of deprecated functions, you can use the `lifecycle_verbosity` option to make deprecated functions warn every time: ```{r} options(lifecycle_verbosity = "warning") df1 <- data_frame(x = 1, y = 2) df2 <- data_frame(a = "apple", b = "banana") ``` Then use `lifecycle::last_lifecycle_warnings()` to track down the source. Alternatively, if you want to be really strict, you can turn all deprecation warnings into errors, forcing you to deal with them immediately: ```{r, error = TRUE} options("lifecycle_verbosity" = "error") df1 <- data_frame(x = 1, y = 2) ```