lifecycle

$npx mdskill add posit-dev/skills/lifecycle

Manage R package lifecycle using tidyverse conventions and the lifecycle package.

  • Sets up lifecycle infrastructure like badges and imports in a package.
  • Depends on the lifecycle R package and usethis for configuration.
  • Checks for existing badge files and uses roxygen2 directives to mark stages.
  • Provides roxygen2 snippets and badge insertion for documentation.

SKILL.md

.github/skills/lifecycleView on GitHub ↗
---
name: lifecycle
description: "Guidance for managing R package lifecycle according to tidyverse principles using the lifecycle package. Use when: (1) Setting up lifecycle infrastructure in a package, (2) Deprecating functions or arguments, (3) Renaming functions or arguments, (4) Superseding functions, (5) Marking functions as experimental, (6) Understanding lifecycle stages (stable, experimental, deprecated, superseded), or (7) Writing deprecation helpers for complex scenarios."
metadata:
  author: Garrick Aden-Buie (@gadenbuie)
  version: "1.1"
license: MIT
---

# R Package Lifecycle Management

Manage function and argument lifecycle using tidyverse conventions and the lifecycle package.

## Setup

Check if lifecycle is configured by looking for `lifecycle-*.svg` files in `man/figures/`.

If not configured, run:

```r
usethis::use_lifecycle()
```

This:

- Adds lifecycle to `Imports` in DESCRIPTION
- Adds `@importFrom lifecycle deprecated` to the package documentation file
- Copies badge SVGs to `man/figures/`

## Lifecycle Badges

Insert badges in roxygen2 documentation:

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

For arguments:

```r
#' @param old_arg `r lifecycle::badge("deprecated")` Use `new_arg` instead.
```

Only badge functions/arguments whose stage differs from the package's overall stage.

## Deprecating a Function

1. Add badge and explanation to `@description`:

```r
#' Do something
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `old_fun()` was deprecated in mypkg 1.0.0. Use [new_fun()] instead.
#' @keywords internal
```

2. Add `deprecate_warn()` as first line of function body:

```r
old_fun <- function(x) {

lifecycle::deprecate_warn("1.0.0", "old_fun()", "new_fun()")
new_fun(x)
}
```

3. Show migration in examples:

```r
#' @examples
#' old_fun(x)
#' # ->
#' new_fun(x)
```

## Deprecation Functions

| Function           | When to Use                                             |
| ------------------ | ------------------------------------------------------- |
| `deprecate_soft()` | First stage; warns only direct users and during tests   |
| `deprecate_warn()` | Standard deprecation; warns once per 8 hours            |
| `deprecate_stop()` | Final stage before removal; errors with helpful message |

**Deprecation workflow for major releases:**

1. Search `deprecate_stop()` - consider removing function entirely
2. Replace `deprecate_warn()` with `deprecate_stop()`
3. Replace `deprecate_soft()` with `deprecate_warn()`

## Renaming a Function

Move implementation to new name, call from old name with deprecation:

```r
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `add_two()` was renamed to `number_add()` for API consistency.
#' @keywords internal
#' @export
add_two <- function(x, y) {
lifecycle::deprecate_warn("1.0.0", "add_two()", "number_add()")
number_add(x, y)
}

#' Add two numbers
#' @export
number_add <- function(x, y) {
x + y
}
```

## Deprecating an Argument

Use `deprecated()` as default value with `is_present()` check:

```r
#' @param path `r lifecycle::badge("deprecated")` Use `file` instead.
write_file <- function(x, file, path = deprecated()) {
  if (lifecycle::is_present(path)) {
    lifecycle::deprecate_warn("1.4.0", "write_file(path)", "write_file(file)")
    file <- path
  }
  # ... rest of function
}
```

## Renaming an 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
  }
  sum(x, y, na.rm = na_rm)
}
```

## Superseding a Function

For functions with better alternatives that shouldn't be removed:

```r
#' Gather columns into key-value pairs
#'
#' @description
#' `r lifecycle::badge("superseded")`
#'
#' Development on `gather()` is complete. For new code, use [pivot_longer()].
#'
#' `df %>% gather("key", "value", x, y, z)` is equivalent to
#' `df %>% pivot_longer(c(x, y, z), names_to = "key", values_to = "value")`.
```

No warning needed - just document the preferred alternative.

## Marking as Experimental

```r
#' @description
#' `r lifecycle::badge("experimental")`
cool_function <- function() {
  lifecycle::signal_stage("experimental", "cool_function()")
  # ...
}
```

## Testing Deprecations

Test that deprecated functions work and warn appropriately:

```r
test_that("old_fun is deprecated", {
  expect_snapshot({
    x <- old_fun(1)
    expect_equal(x, expected_value)
  })
})
```

Suppress warnings in existing tests:

```r
test_that("old_fun returns correct value", {
  withr::local_options(lifecycle_verbosity = "quiet")
  expect_equal(old_fun(1), expected_value)
})
```

## Deprecation Helpers

For deprecations affecting many functions (e.g., removing a common argument), create an internal helper:

```r
warn_for_verbose <- function(
  verbose = TRUE,
  env = rlang::caller_env(),
  user_env = rlang::caller_env(2)
) {
  if (!lifecycle::is_present(verbose) || isTRUE(verbose)) {
    return(invisible())
  }

  lifecycle::deprecate_warn(
    when = "2.0.0",
    what = I("The `verbose` argument"),
    details = c(
      "Set `options(mypkg_quiet = TRUE)` to suppress messages.",
      "The `verbose` argument will be removed in a future release."
    ),
    user_env = user_env
  )

  invisible()
}
```

Then use in affected functions:

```r
my_function <- function(..., verbose = deprecated()) {
  warn_for_verbose(verbose)
  # ...
}
```

## Custom Deprecation Messages

For non-standard deprecations, use `I()` to wrap custom text:

```r
lifecycle::deprecate_warn(
  when = "1.0.0",
  what = I('Setting option "pkg.opt" to "foo"'),
  with = I('"pkg.new_opt"')
)
```

The `what` fragment must work with "was deprecated in..." appended.

## Reference

See `references/lifecycle-stages.md` for detailed stage definitions and transitions.

More from posit-dev/skills

SkillDescription
alt-text>
brand-ymlCreate and use brand.yml files for consistent branding across Shiny apps and Quarto documents. Covers: (1) Creating new _brand.yml files, (2) Applying to Shiny (R and Python), (3) Using in Quarto, (4) Modifying existing files, and (5) Troubleshooting. Includes complete specifications and integration guides.
cli>
cran-extrachecks>
create-release-checklist>
critical-code-reviewerConduct rigorous, adversarial code reviews with zero tolerance for mediocrity. Use when users ask to "critically review" my code or a PR, "critique my code", "find issues in my code", or "what's wrong with this code". Identifies security holes, lazy patterns, edge case failures, and bad practices across Python, R, JavaScript/TypeScript, SQL, and front-end code. Scrutinizes error handling, type safety, performance, accessibility, and code quality. Provides structured feedback with severity tiers (Blocking, Required, Suggestions) and specific, actionable recommendations.
describe-designResearch a codebase and create architectural documentation describing how features or systems work. Use when the user asks to: (1) Document how a feature works, (2) Create an architecture overview, (3) Explain code structure for onboarding or knowledge transfer, (4) Research and describe a system's design. Produces markdown documents with Mermaid diagrams and stable code references suitable for humans and AI agents.
ggsqlWrite ggsql queries — a grammar of graphics for SQL. Use when the user wants to create, modify, or understand a ggsql visualization query.
implementOrchestrates implementation of a plan file by delegating work to subagents in parallel. Verifies git branch state, tracks progress, and ensures high-quality implementation. Invoke with a plan file path and optional model override: /implement plans/my-plan.md [--model sonnet]
maintainer-declineGuide for drafting issue closure and decline responses as an open-source package maintainer. Use when helping compose a reply that says \"no\" to a feature request, closes an issue as won't-fix, redirects a user to a different package, explains why a design choice is intentional, or otherwise declines or closes a community contribution. Also use when the maintainer needs to explain a deprecation, point out a user misunderstanding, or communicate an effort/scope tradeoff to a contributor.