Title: | 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. |
Authors: | Lionel Henry [aut, cre], Hadley Wickham [aut] , Posit Software, PBC [cph, fnd] |
Maintainer: | Lionel Henry <[email protected]> |
License: | MIT + file LICENSE |
Version: | 1.0.4 |
Built: | 2024-10-24 05:14:54 UTC |
Source: | https://github.com/r-lib/lifecycle |
To include lifecycle badges in your documentation:
Call usethis::use_lifecycle()
to copy the badge images into the
man/
folder of your package.
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.
badge(stage)
badge(stage)
stage |
A lifecycle stage as a string. Must be one of
|
An Rd
expression describing the lifecycle stage.
The meaning of these stages is described in
vignette("stages")
.
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
directly, i.e. a user is calling a function they wrote in the global
environment or a developer is calling it in their package. It does not
warn when called indirectly, i.e. the deprecation comes from code that
you don't control.
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)
.
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, always = FALSE, env = caller_env(), user_env = caller_env(2) ) deprecate_stop(when, what, with = NULL, details = NULL, env = caller_env())
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, always = FALSE, env = caller_env(), user_env = caller_env(2) ) deprecate_stop(when, what, with = NULL, details = NULL, env = caller_env())
when |
A string giving the version when the behaviour was deprecated. |
what |
A string describing what is deprecated:
You can optionally supply the namespace: |
with |
An optional string giving a recommended replacement for the
deprecated behaviour. This takes the same form as |
details |
In most cases the deprecation message can be
automatically generated from
|
id |
The id of the deprecation. A warning is issued only once
for each |
env , user_env
|
Pair of environments that define where These are only needed if you're calling |
always |
If |
NULL
, invisibly.
Deprecation warnings have class lifecycle_warning_deprecated
.
Deprecation errors have class lifecycle_error_deprecated
.
# 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?" ) )
# 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?" ) )
Signal deprecated argument by using self-documenting sentinel
deprecated()
as default argument. Test whether the caller has
supplied the argument with is_present()
.
deprecated() is_present(arg)
deprecated() is_present(arg)
arg |
A |
We recommend importing lifecycle::deprecated()
in your namespace
and use it without the namespace qualifier.
In general, we advise against 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.
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)
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)
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()) })
expect_deprecated(expr, regexp = NULL, ...) expect_defunct(expr)
expect_deprecated(expr, regexp = NULL, ...) expect_defunct(expr)
expr |
Expression that should produce a lifecycle warning or error. |
regexp |
Optional regular expression matched against the expected warning message. |
... |
Arguments passed on to
|
expect_deprecated()
sets the lifecycle_verbosity
option to "warning"
to enforce deprecation warnings which are
otherwise only shown once every 8 hours.
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).
last_lifecycle_warnings()
last_lifecycle_warnings()
# These examples are not run because `last_lifecycle_warnings()` does not # work well within knitr and pkgdown ## Not run: 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") ## End(Not run)
# These examples are not run because `last_lifecycle_warnings()` does not # work well within knitr and pkgdown ## Not run: 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") ## End(Not run)
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.
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") )
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") )
package |
The name of an installed package. |
which |
The lifecycle statuses to retrieve.
Include |
packages |
One or more installed packages to query for lifecycle statuses. |
path |
The directory path to the files you want to search. |
pattern |
Any files matching this pattern will be searched. The default
searches any files ending in |
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.
signal_stage(stage, what, with = NULL, env = caller_env())
signal_stage(stage, what, with = NULL, env = caller_env())
stage |
Life cycle stage, either "experimental" or "superseded". |
what |
String describing what feature the stage applies too, using
the same syntax as |
with |
An optional string giving a recommended replacement for a superseded function. |
env |
Environment used to determine where |
foofy <- function(x, y, z) { signal_stage("experimental", "foofy()") x + y / z } foofy(1, 2, 3)
foofy <- function(x, y, z) { signal_stage("experimental", "foofy()") x + y / z } foofy(1, 2, 3)
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).
You can control the level of verbosity with the global option
lifecycle_verbosity
. It can be set to:
"quiet"
to suppress all deprecation messages.
"default"
or NULL
to warn once every 8 hours.
"warning"
to warn every time.
"error"
to error instead of warning.
Note that functions calling deprecate_stop()
invariably throw
errors.
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) }) }
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) }) }