From 47c4c9d07d2467a1f6541d15ea93118a6ada4d61 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 25 Nov 2025 12:49:28 +0100 Subject: [PATCH 01/18] basic utilities --- R/ggplot-global.R | 2 ++ R/utilities-edition.R | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 R/utilities-edition.R diff --git a/R/ggplot-global.R b/R/ggplot-global.R index 495dc65ae0..dfcbbbccc9 100644 --- a/R/ggplot-global.R +++ b/R/ggplot-global.R @@ -52,3 +52,5 @@ ggplot_global$x_aes <- c("x", "xmin", "xmax", "xend", "xintercept", ggplot_global$y_aes <- c("y", "ymin", "ymax", "yend", "yintercept", "ymin_final", "ymax_final", "lower", "middle", "upper", "y0") + +ggplot_global$edition <- NULL diff --git a/R/utilities-edition.R b/R/utilities-edition.R new file mode 100644 index 0000000000..294c544720 --- /dev/null +++ b/R/utilities-edition.R @@ -0,0 +1,31 @@ +# Any new editions should be appended here and anchored to a version +edition_versions <- c( + "2026" = "4.1.0" +) + +get_edition <- function() { + ggplot_global$edition[[1]] +} + +set_edition <- function(edition = NULL) { + ggplot_global$edition <- validate_edition(edition) + invisible() +} + +validate_edition <- function(edition, allow_null = TRUE, call = caller_env()) { + if (is.null(edition) && allow_null) { + return(NULL) + } + check_string(edition, allow_empty = FALSE, call = call) + arg_match0(edition, names(edition_versions), error_call = call) +} + +edition_require <- function(edition = NULL, what, call = caller_env()) { + if (identical(get_edition(), validate_edition(edition))) { + return(invisible()) + } + cli::cli_abort( + "{what} requires the {edition} edition of {.pkg ggplot2}.", + call = call + ) +} From 2e5bac70cc1ba7b0eea9e1b9f253be009e8fe91d Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 25 Nov 2025 13:00:35 +0100 Subject: [PATCH 02/18] tie in editions with deprecation --- R/utilities.R | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/R/utilities.R b/R/utilities.R index 598b9a8fc3..13b00da222 100644 --- a/R/utilities.R +++ b/R/utilities.R @@ -802,6 +802,11 @@ deprecate <- function(when, ..., id = NULL, always = FALSE, user_env = NULL, defunct <- "0.0.0" } + edition <- get_edition() + if (!is.null(edition) && edition %in% names(edition_versions)) { + soft <- full <- defunct <- edition_versions[[edition]] + } + version <- as.package_version(when) if (version < defunct || identical(escalate, "abort")) { lifecycle::deprecate_stop(when, ...) From 9cf93a5a02e969804d4a9306e9249f7fc5159a7a Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 25 Nov 2025 13:02:29 +0100 Subject: [PATCH 03/18] add 2025 edition for testing purposes --- R/utilities-edition.R | 1 + 1 file changed, 1 insertion(+) diff --git a/R/utilities-edition.R b/R/utilities-edition.R index 294c544720..16da8b7c77 100644 --- a/R/utilities-edition.R +++ b/R/utilities-edition.R @@ -1,5 +1,6 @@ # Any new editions should be appended here and anchored to a version edition_versions <- c( + "2025" = "4.0.0", "2026" = "4.1.0" ) From 725cdbb7939e822d02c3f52a3ef470d823dccd4e Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 25 Nov 2025 14:20:51 +0100 Subject: [PATCH 04/18] add utility for supersession --- R/utilities.R | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/R/utilities.R b/R/utilities.R index 13b00da222..d98a512677 100644 --- a/R/utilities.R +++ b/R/utilities.R @@ -820,6 +820,28 @@ deprecate <- function(when, ..., id = NULL, always = FALSE, user_env = NULL, invisible() } +supersede <- function(edition, what, with = NULL, ..., env = caller_env()) { + current_edition <- get_edition() + if ( + !is.null(current_edition) && + current_edition %in% names(edition_versions) && + as.numeric(current_edition) >= as.numeric(edition) + ) { + lifecycle::deprecate_stop( + when = paste0("edition ", edition), + what = what, + with = with, + env = env + ) + } + lifecycle::signal_stage( + stage = "superseded", + what = what, + with = with, + env = env + ) +} + as_unordered_factor <- function(x) { x <- as.factor(x) class(x) <- setdiff(class(x), "ordered") From b2e9f0d83d6ef0b7af6d9063554a60cb61ad75a4 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 25 Nov 2025 14:27:49 +0100 Subject: [PATCH 05/18] use supersede utility --- R/annotation-logticks.R | 2 +- R/coord-flip.R | 2 +- R/coord-map.R | 2 +- R/coord-polar.R | 2 +- R/labels.R | 6 +++--- R/limits.R | 3 ++- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/R/annotation-logticks.R b/R/annotation-logticks.R index 548a89470b..7757bafa45 100644 --- a/R/annotation-logticks.R +++ b/R/annotation-logticks.R @@ -93,7 +93,7 @@ annotation_logticks <- function(base = 10, sides = "bl", outside = FALSE, scaled if (!is.null(color)) colour <- color - lifecycle::signal_stage("superseded", "annotation_logticks()", "guide_axis_logticks()") + supersede("2026", "annotation_logticks()", "guide_axis_logticks()") if (lifecycle::is_present(size)) { deprecate("3.5.0", I("Using the `size` aesthetic in this geom"), I("`linewidth`")) diff --git a/R/coord-flip.R b/R/coord-flip.R index 3ea68a8273..f19b75c64d 100644 --- a/R/coord-flip.R +++ b/R/coord-flip.R @@ -57,7 +57,7 @@ #' geom_area() + #' coord_flip() coord_flip <- function(xlim = NULL, ylim = NULL, expand = TRUE, clip = "on") { - lifecycle::signal_stage("superseded", "coord_flip()") + supersede("2026", "coord_flip()", with = I("swapping x and y aesthetics")) check_coord_limits(xlim) check_coord_limits(ylim) ggproto(NULL, CoordFlip, diff --git a/R/coord-map.R b/R/coord-map.R index b23ca8abad..50938617f2 100644 --- a/R/coord-map.R +++ b/R/coord-map.R @@ -136,7 +136,7 @@ coord_map <- function(projection="mercator", ..., parameters = NULL, orientation } else { params <- parameters } - lifecycle::signal_stage("superseded", "coord_map()", "coord_sf()") + supersede("2026", "coord_map()", "coord_sf()") check_coord_limits(xlim) check_coord_limits(ylim) diff --git a/R/coord-polar.R b/R/coord-polar.R index 107cbf0f74..b4763da20f 100644 --- a/R/coord-polar.R +++ b/R/coord-polar.R @@ -3,7 +3,7 @@ coord_polar <- function(theta = "x", start = 0, direction = 1, clip = "on") { theta <- arg_match0(theta, c("x", "y")) r <- if (theta == "x") "y" else "x" - lifecycle::signal_stage("superseded", "coord_polar()", "coord_radial()") + supersede("2026", "coord_polar()", "coord_radial()") ggproto(NULL, CoordPolar, theta = theta, diff --git a/R/labels.R b/R/labels.R index 87e5bd2d63..f1d233dff3 100644 --- a/R/labels.R +++ b/R/labels.R @@ -239,21 +239,21 @@ labs <- function(..., title = waiver(), subtitle = waiver(), #' superseded. It is recommended to use the `labs(x, y, title, subtitle)` #' arguments instead. xlab <- function(label) { - lifecycle::signal_stage("superseded", "xlab()", "labs(x)") + supersede("2026", "xlab()", "labs(x)") labs(x = label) } #' @rdname labs #' @export ylab <- function(label) { - lifecycle::signal_stage("superseded", "ylab()", "labs(y)") + upersede("2026", "ylab()", "labs(y)") labs(y = label) } #' @rdname labs #' @export ggtitle <- function(label, subtitle = waiver()) { - lifecycle::signal_stage("superseded", "ggtitle()", I("labs(title, subtitle)")) + upersede("2026", "ggtitle()", I("labs(title, subtitle)")) labs(title = label, subtitle = subtitle) } diff --git a/R/limits.R b/R/limits.R index 976307a467..25d4a1d4f9 100644 --- a/R/limits.R +++ b/R/limits.R @@ -184,7 +184,8 @@ limits.POSIXlt <- function(lims, var, call = caller_env()) { expand_limits <- function(...) { data <- list2(...) - lifecycle::signal_stage("superseded", "expand_limits()") + + supersede("2026", "expand_limits()") # unpack data frame columns data_dfs <- vapply(data, is.data.frame, logical(1)) From 7bdb67efe3b002c5e868e277b1c5dbe254b72fb8 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 25 Nov 2025 14:28:35 +0100 Subject: [PATCH 06/18] move `deprecate()` and `supersede()` --- R/utilities-edition.R | 53 +++++++++++++++++++++++++++++++++++++++++++ R/utilities.R | 53 ------------------------------------------- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/R/utilities-edition.R b/R/utilities-edition.R index 16da8b7c77..694d2e34a2 100644 --- a/R/utilities-edition.R +++ b/R/utilities-edition.R @@ -30,3 +30,56 @@ edition_require <- function(edition = NULL, what, call = caller_env()) { call = call ) } + +deprecate <- function(when, ..., id = NULL, always = FALSE, user_env = NULL, + escalate = NULL) { + + defunct <- "3.0.0" + full <- "3.4.0" + soft <- utils::packageVersion("ggplot2") + + if (identical(escalate, "delay")) { + soft <- full + full <- defunct + defunct <- "0.0.0" + } + + edition <- get_edition() + if (!is.null(edition) && edition %in% names(edition_versions)) { + soft <- full <- defunct <- edition_versions[[edition]] + } + + version <- as.package_version(when) + if (version < defunct || identical(escalate, "abort")) { + lifecycle::deprecate_stop(when, ...) + } + user_env <- user_env %||% getOption("ggplot2_plot_env") %||% caller_env(2) + if (version <= full || identical(escalate, "warn")) { + lifecycle::deprecate_warn(when, ..., id = id, always = always, user_env = user_env) + } else if (version <= soft) { + lifecycle::deprecate_soft(when, ..., id = id, user_env = user_env) + } + invisible() +} + +supersede <- function(edition, what, with = NULL, ..., env = caller_env()) { + current_edition <- get_edition() + if ( + !is.null(current_edition) && + current_edition %in% names(edition_versions) && + as.numeric(current_edition) >= as.numeric(edition) + ) { + lifecycle::deprecate_stop( + when = paste0("edition ", edition), + what = what, + with = with, + env = env + ) + } + lifecycle::signal_stage( + stage = "superseded", + what = what, + with = with, + env = env + ) +} diff --git a/R/utilities.R b/R/utilities.R index d98a512677..4b72934d36 100644 --- a/R/utilities.R +++ b/R/utilities.R @@ -789,59 +789,6 @@ as_cli <- function(..., env = caller_env()) { cli::cli_fmt(cli::cli_text(..., .envir = env)) } -deprecate <- function(when, ..., id = NULL, always = FALSE, user_env = NULL, - escalate = NULL) { - - defunct <- "3.0.0" - full <- "3.4.0" - soft <- utils::packageVersion("ggplot2") - - if (identical(escalate, "delay")) { - soft <- full - full <- defunct - defunct <- "0.0.0" - } - - edition <- get_edition() - if (!is.null(edition) && edition %in% names(edition_versions)) { - soft <- full <- defunct <- edition_versions[[edition]] - } - - version <- as.package_version(when) - if (version < defunct || identical(escalate, "abort")) { - lifecycle::deprecate_stop(when, ...) - } - user_env <- user_env %||% getOption("ggplot2_plot_env") %||% caller_env(2) - if (version <= full || identical(escalate, "warn")) { - lifecycle::deprecate_warn(when, ..., id = id, always = always, user_env = user_env) - } else if (version <= soft) { - lifecycle::deprecate_soft(when, ..., id = id, user_env = user_env) - } - invisible() -} - -supersede <- function(edition, what, with = NULL, ..., env = caller_env()) { - current_edition <- get_edition() - if ( - !is.null(current_edition) && - current_edition %in% names(edition_versions) && - as.numeric(current_edition) >= as.numeric(edition) - ) { - lifecycle::deprecate_stop( - when = paste0("edition ", edition), - what = what, - with = with, - env = env - ) - } - lifecycle::signal_stage( - stage = "superseded", - what = what, - with = with, - env = env - ) -} - as_unordered_factor <- function(x) { x <- as.factor(x) class(x) <- setdiff(class(x), "ordered") From 5774a42009fe9558f29da7590102b18d0fd99639 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 25 Nov 2025 14:30:59 +0100 Subject: [PATCH 07/18] fix typos --- R/labels.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/labels.R b/R/labels.R index f1d233dff3..ab47b2a72d 100644 --- a/R/labels.R +++ b/R/labels.R @@ -246,14 +246,14 @@ xlab <- function(label) { #' @rdname labs #' @export ylab <- function(label) { - upersede("2026", "ylab()", "labs(y)") + supersede("2026", "ylab()", "labs(y)") labs(y = label) } #' @rdname labs #' @export ggtitle <- function(label, subtitle = waiver()) { - upersede("2026", "ggtitle()", I("labs(title, subtitle)")) + supersede("2026", "ggtitle()", I("labs(title, subtitle)")) labs(title = label, subtitle = subtitle) } From c0b8fb3be352b7daee3c3fdac353a0c6f945ee08 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 25 Nov 2025 14:34:30 +0100 Subject: [PATCH 08/18] document `set_edition()` --- DESCRIPTION | 1 + NAMESPACE | 1 + R/utilities-edition.R | 21 +++++++++++++++++++-- man/set_edition.Rd | 24 ++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 man/set_edition.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 01f91fcb9a..506b91eb0f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -279,6 +279,7 @@ Collate: 'theme-current.R' 'theme-sub.R' 'utilities-break.R' + 'utilities-edition.R' 'utilities-grid.R' 'utilities-help.R' 'utilities-patterns.R' diff --git a/NAMESPACE b/NAMESPACE index ce32705b6d..526917cdd9 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -671,6 +671,7 @@ export(scale_y_reverse) export(scale_y_sqrt) export(scale_y_time) export(sec_axis) +export(set_edition) export(set_last_plot) export(set_theme) export(sf_transform_xy) diff --git a/R/utilities-edition.R b/R/utilities-edition.R index 694d2e34a2..e2826e2adc 100644 --- a/R/utilities-edition.R +++ b/R/utilities-edition.R @@ -8,9 +8,26 @@ get_edition <- function() { ggplot_global$edition[[1]] } + +#' Set ggplot2 edition +#' +#' ggplot2 uses the 'edition' concept to manage the lifecycles of functions and +#' arguments. Setting a recent edition opens up the latest features but also +#' closes down deprecated and superseded functionality. +#' +#' @param edition An edition. Possible values currently include `"2026"` only. +#' Can be `NULL` (default) to unset an edition. +#' +#' @returns The previous `edition` value. This function is called for the side +#' effect of setting the edition though. +#' @export +#' +#' @examples +#' set_edition(2026) set_edition <- function(edition = NULL) { - ggplot_global$edition <- validate_edition(edition) - invisible() + old <- ggplot_global$edition + ggplot_global$edition <- validate_edition(as.character(edition)) + invisible(old) } validate_edition <- function(edition, allow_null = TRUE, call = caller_env()) { diff --git a/man/set_edition.Rd b/man/set_edition.Rd new file mode 100644 index 0000000000..2bf0a4f718 --- /dev/null +++ b/man/set_edition.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utilities-edition.R +\name{set_edition} +\alias{set_edition} +\title{Set ggplot2 edition} +\usage{ +set_edition(edition = NULL) +} +\arguments{ +\item{edition}{An edition. Possible values currently include \code{"2026"} only. +Can be \code{NULL} (default) to unset an edition.} +} +\value{ +The previous \code{edition} value. This function is called for the side +effect of setting the edition though. +} +\description{ +ggplot2 uses the 'edition' concept to manage the lifecycles of functions and +arguments. Setting a recent edition opens up the latest features but also +closes down deprecated and superseded functionality. +} +\examples{ +set_edition(2026) +} From 97df19fe9c1a2663cdf000c01dcc9bcf720a4865 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 25 Nov 2025 14:34:50 +0100 Subject: [PATCH 09/18] rename file --- R/{utilities-edition.R => utilities-lifecycle.R} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename R/{utilities-edition.R => utilities-lifecycle.R} (100%) diff --git a/R/utilities-edition.R b/R/utilities-lifecycle.R similarity index 100% rename from R/utilities-edition.R rename to R/utilities-lifecycle.R From dccab718a42af4c8abe23aa6d73affd91ec19ea5 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 25 Nov 2025 15:05:09 +0100 Subject: [PATCH 10/18] redocument --- DESCRIPTION | 2 +- man/set_edition.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 506b91eb0f..d0d2072f3d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -279,9 +279,9 @@ Collate: 'theme-current.R' 'theme-sub.R' 'utilities-break.R' - 'utilities-edition.R' 'utilities-grid.R' 'utilities-help.R' + 'utilities-lifecycle.R' 'utilities-patterns.R' 'utilities-performance.R' 'utilities-resolution.R' diff --git a/man/set_edition.Rd b/man/set_edition.Rd index 2bf0a4f718..981108b083 100644 --- a/man/set_edition.Rd +++ b/man/set_edition.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utilities-edition.R +% Please edit documentation in R/utilities-lifecycle.R \name{set_edition} \alias{set_edition} \title{Set ggplot2 edition} From fefa82d588241e6ef0a7853632f91ee6f1318989 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 25 Nov 2025 14:35:32 +0100 Subject: [PATCH 11/18] user facing funs first --- R/utilities-lifecycle.R | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/R/utilities-lifecycle.R b/R/utilities-lifecycle.R index e2826e2adc..11c369bfe1 100644 --- a/R/utilities-lifecycle.R +++ b/R/utilities-lifecycle.R @@ -1,14 +1,3 @@ -# Any new editions should be appended here and anchored to a version -edition_versions <- c( - "2025" = "4.0.0", - "2026" = "4.1.0" -) - -get_edition <- function() { - ggplot_global$edition[[1]] -} - - #' Set ggplot2 edition #' #' ggplot2 uses the 'edition' concept to manage the lifecycles of functions and @@ -30,6 +19,16 @@ set_edition <- function(edition = NULL) { invisible(old) } +get_edition <- function() { + ggplot_global$edition[[1]] +} + +# Any new editions should be appended here and anchored to a version +edition_versions <- c( + "2025" = "4.0.0", + "2026" = "4.1.0" +) + validate_edition <- function(edition, allow_null = TRUE, call = caller_env()) { if (is.null(edition) && allow_null) { return(NULL) From ca1f39e03fc1d133aa3e6e7a032cb4459de52e7d Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 25 Nov 2025 14:39:30 +0100 Subject: [PATCH 12/18] fix bug --- R/utilities-lifecycle.R | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/R/utilities-lifecycle.R b/R/utilities-lifecycle.R index 11c369bfe1..ab911b9364 100644 --- a/R/utilities-lifecycle.R +++ b/R/utilities-lifecycle.R @@ -38,7 +38,9 @@ validate_edition <- function(edition, allow_null = TRUE, call = caller_env()) { } edition_require <- function(edition = NULL, what, call = caller_env()) { - if (identical(get_edition(), validate_edition(edition))) { + edition <- validate_edition(edition) + current_edition <- get_edition() + if (as.numeric(current_edition) >= as.numeric(edition)) { return(invisible()) } cli::cli_abort( From 7318b84196230f2d465ec2bf3e79d251d6619534 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 25 Nov 2025 14:41:39 +0100 Subject: [PATCH 13/18] avoid `as.character(NULL)` --- R/utilities-lifecycle.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/utilities-lifecycle.R b/R/utilities-lifecycle.R index ab911b9364..fe091e95d2 100644 --- a/R/utilities-lifecycle.R +++ b/R/utilities-lifecycle.R @@ -15,7 +15,7 @@ #' set_edition(2026) set_edition <- function(edition = NULL) { old <- ggplot_global$edition - ggplot_global$edition <- validate_edition(as.character(edition)) + ggplot_global$edition <- validate_edition(edition) invisible(old) } @@ -33,6 +33,7 @@ validate_edition <- function(edition, allow_null = TRUE, call = caller_env()) { if (is.null(edition) && allow_null) { return(NULL) } + edition <- as.character(edition) check_string(edition, allow_empty = FALSE, call = call) arg_match0(edition, names(edition_versions), error_call = call) } From 90adcd04506c49e6743edbfe0b18b08d3716ca40 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 25 Nov 2025 14:57:08 +0100 Subject: [PATCH 14/18] add tests --- tests/testthat/_snaps/utilities-lifecycle.md | 31 +++++++++++++++ tests/testthat/test-utilities-lifecycle.R | 41 ++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 tests/testthat/_snaps/utilities-lifecycle.md create mode 100644 tests/testthat/test-utilities-lifecycle.R diff --git a/tests/testthat/_snaps/utilities-lifecycle.md b/tests/testthat/_snaps/utilities-lifecycle.md new file mode 100644 index 0000000000..003ef199ac --- /dev/null +++ b/tests/testthat/_snaps/utilities-lifecycle.md @@ -0,0 +1,31 @@ +# editions can be set and unset + + Code + set_edition("nonsense") + Condition + Error in `set_edition()`: + ! `edition` must be one of "2025" or "2026", not "nonsense". + +# edition deprecation works + + `foo()` was deprecated in ggplot2 4.0.0. + i Please use `bar()` instead. + +--- + + Code + foo() + Condition + Error: + ! `foo()` was deprecated in ggplot2 4.0.0 and is now defunct. + i Please use `bar()` instead. + +# edition supersession works + + Code + foo() + Condition + Error: + ! `foo()` was deprecated in edition 2025 and is now defunct. + i Please use `bar()` instead. + diff --git a/tests/testthat/test-utilities-lifecycle.R b/tests/testthat/test-utilities-lifecycle.R new file mode 100644 index 0000000000..31cdbedda4 --- /dev/null +++ b/tests/testthat/test-utilities-lifecycle.R @@ -0,0 +1,41 @@ +test_that("editions can be set and unset", { + + x <- set_edition(2026) + expect_null(x) # Set edition returns old value + expect_equal(get_edition(), "2026") + + x <- set_edition(NULL) + expect_equal(x, "2026") + expect_equal(get_edition(), NULL) + + # Test invalid values + expect_snapshot( + set_edition("nonsense"), + error = TRUE + ) +}) + +test_that("edition deprecation works", { + foo <- function() { + deprecate("4.0.0", what = "foo()", with = "bar()") + } + expect_snapshot_warning(foo()) + + set_edition(2025) + withr::defer(set_edition()) + + expect_snapshot(foo(), error = TRUE) +}) + +test_that("edition supersession works", { + foo <- function() { + supersede("2025", what = "foo()", with = "bar()") + NULL + } + expect_silent(foo()) + + set_edition(2025) + withr::defer(set_edition()) + + expect_snapshot(foo(), error = TRUE) +}) From 8b182b2508357ff484f1ed12db0edba70a4762e2 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 25 Nov 2025 14:57:15 +0100 Subject: [PATCH 15/18] fix bug --- R/utilities-lifecycle.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/utilities-lifecycle.R b/R/utilities-lifecycle.R index fe091e95d2..023cfc1ffb 100644 --- a/R/utilities-lifecycle.R +++ b/R/utilities-lifecycle.R @@ -69,7 +69,7 @@ deprecate <- function(when, ..., id = NULL, always = FALSE, user_env = NULL, } version <- as.package_version(when) - if (version < defunct || identical(escalate, "abort")) { + if (version <= defunct || identical(escalate, "abort")) { lifecycle::deprecate_stop(when, ...) } user_env <- user_env %||% getOption("ggplot2_plot_env") %||% caller_env(2) From 0c07343701be2c9737f0938168f4c146556fec81 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 25 Nov 2025 15:32:27 +0100 Subject: [PATCH 16/18] fix bug --- R/utilities-lifecycle.R | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/R/utilities-lifecycle.R b/R/utilities-lifecycle.R index 023cfc1ffb..54a4709bb4 100644 --- a/R/utilities-lifecycle.R +++ b/R/utilities-lifecycle.R @@ -41,7 +41,10 @@ validate_edition <- function(edition, allow_null = TRUE, call = caller_env()) { edition_require <- function(edition = NULL, what, call = caller_env()) { edition <- validate_edition(edition) current_edition <- get_edition() - if (as.numeric(current_edition) >= as.numeric(edition)) { + if ( + !is.null(current_edition) && + as.numeric(current_edition) >= as.numeric(edition) + ) { return(invisible()) } cli::cli_abort( From df4c1019bd8798e6241942fc562062568a9b8661 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 25 Nov 2025 15:32:42 +0100 Subject: [PATCH 17/18] include test for `edition_require()` --- tests/testthat/_snaps/utilities-lifecycle.md | 8 ++++++++ tests/testthat/test-utilities-lifecycle.R | 15 +++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/tests/testthat/_snaps/utilities-lifecycle.md b/tests/testthat/_snaps/utilities-lifecycle.md index 003ef199ac..87255851d8 100644 --- a/tests/testthat/_snaps/utilities-lifecycle.md +++ b/tests/testthat/_snaps/utilities-lifecycle.md @@ -29,3 +29,11 @@ ! `foo()` was deprecated in edition 2025 and is now defunct. i Please use `bar()` instead. +# edition requirements work + + Code + foo() + Condition + Error in `foo()`: + ! foo() requires the 2025 edition of ggplot2. + diff --git a/tests/testthat/test-utilities-lifecycle.R b/tests/testthat/test-utilities-lifecycle.R index 31cdbedda4..ad93d50158 100644 --- a/tests/testthat/test-utilities-lifecycle.R +++ b/tests/testthat/test-utilities-lifecycle.R @@ -39,3 +39,18 @@ test_that("edition supersession works", { expect_snapshot(foo(), error = TRUE) }) + +test_that("edition requirements work", { + + foo <- function() { + edition_require("2025", what = "foo()") + NULL + } + + expect_snapshot(foo(), error = TRUE) + + set_edition(2025) + withr::defer(set_edition()) + + expect_silent(foo()) +}) From 012c3e2b747773804b4506752ec01060b62d9c92 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 25 Nov 2025 15:53:51 +0100 Subject: [PATCH 18/18] unrustle pkgdown's jimmies --- R/utilities-lifecycle.R | 1 + man/set_edition.Rd | 1 + 2 files changed, 2 insertions(+) diff --git a/R/utilities-lifecycle.R b/R/utilities-lifecycle.R index 54a4709bb4..0173829766 100644 --- a/R/utilities-lifecycle.R +++ b/R/utilities-lifecycle.R @@ -10,6 +10,7 @@ #' @returns The previous `edition` value. This function is called for the side #' effect of setting the edition though. #' @export +#' @keywords internal #' #' @examples #' set_edition(2026) diff --git a/man/set_edition.Rd b/man/set_edition.Rd index 981108b083..dbbcc566a3 100644 --- a/man/set_edition.Rd +++ b/man/set_edition.Rd @@ -22,3 +22,4 @@ closes down deprecated and superseded functionality. \examples{ set_edition(2026) } +\keyword{internal}