When shifting from an analysis script to package code, it is often helpful to turn:
library(dplyr)
library(ggplot2)
iris |>
mutate(something = seq_len(nrow(iris)) |>
ggplot() +
geom_point(...)
into:
library(dplyr)
library(ggplot2)
iris |>
dplyr::mutate(something = seq_len(nrow(iris)) |>
ggplot2::ggplot() +
ggplot2::geom_point(...)
to allow easy copy-pasting into the package.
It would be great to have a rule to make package names explicit in this way. I've given it a go, since prototypes are very cheap now. It implements this with a few specific choices to make it consistent:
- If a function has multiple package sources, don't try to resolve it.
- If a function has one package source, but is re-exported (e.g.,
tibble()), then resolve to the original (tibble() -> tibble, not to dplyr if both are loaded. Goes to dplyr if tibble is not loaded.)
- If a function has a local source, treat that as the truth.
Proof of concept
Input:
library(dplyr)
library(tibble)
library(rlang)
# Clear dplyr calls should be flagged and fixed.
x <- tibble(a = 1:5, g = c("a", "a", "b", "b", "b"))
y <- mutate(x, b = a + 1)
z <- summarise(group_by(y, g), total = sum(b))
# Re-exported calls should resolve to the provider package, not the re-exporter.
nested <- tibble(
id = 1:3,
label = as_label(expr(id))
)
# Already explicit calls should be left alone.
explicit <- dplyr::mutate(x, c = a * 2)
# Default R packages should be left alone.
base_calls <- list(mean(x$a), median(x$a), sum(x$a))
# This is ambiguous between stats and dplyr, so the rule should report without a fix.
maybe_ambiguous <- filter(x, a > 2)
# Local definitions should be left alone even if a package exports the same name.
tibble <- function(value) {
value
}
local_shadow <- tibble("not a package call")
# Calls with internal comments should preserve comments when fixed.
with_comment <- mutate(
x,
# keep this comment in place
d = a + 10
)
Output, with fixing:
library(dplyr)
library(tibble)
library(rlang)
# Clear dplyr calls should be flagged and fixed.
x <- tibble::tibble(a = 1:5, g = c("a", "a", "b", "b", "b"))
y <- dplyr::mutate(x, b = a + 1)
z <- dplyr::summarise(dplyr::group_by(y, g), total = sum(b))
# Re-exported calls should resolve to the provider package, not the re-exporter.
nested <- tibble::tibble(
id = 1:3,
label = rlang::as_label(rlang::expr(id))
)
# Already explicit calls should be left alone.
explicit <- dplyr::mutate(x, c = a * 2)
# Default R packages should be left alone.
base_calls <- list(mean(x$a), median(x$a), sum(x$a))
# This is ambiguous between stats and dplyr, so the rule should report without a fix.
maybe_ambiguous <- filter(x, a > 2)
# Local definitions should be left alone even if a package exports the same name.
tibble <- function(value) {
value
}
local_shadow <- tibble("not a package call")
# Calls with internal comments should preserve comments when fixed.
with_comment <- dplyr::mutate(
x,
# keep this comment in place
d = a + 10
)
Console notes:
warning: explicit_packages
--> explicit_packages_stretch.R:23:20
|
23 | maybe_ambiguous <- filter(x, a > 2)
| ------ Cannot choose an explicit package qualifier for `filter()`.
|
= help: `filter()` is exported by multiple loaded packages: stats, dplyr.
When shifting from an analysis script to package code, it is often helpful to turn:
into:
to allow easy copy-pasting into the package.
It would be great to have a rule to make package names explicit in this way. I've given it a go, since prototypes are very cheap now. It implements this with a few specific choices to make it consistent:
tibble()), then resolve to the original (tibble()->tibble, not todplyrif both are loaded. Goes todplyriftibbleis not loaded.)Proof of concept
Input:
Output, with fixing:
Console notes: