Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lintr >= 3.0.0 support #180

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
^pkgdown$
^lifecycle\.Rproj$
^\.Rproj\.user$
^\.idea$
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NB this is for PyCharm

^README\.Rmd$
^cran-comments\.md$
^CRAN-RELEASE$
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.idea
.Rproj.user
inst/doc
docs
9 changes: 5 additions & 4 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: lifecycle
Title: Manage the Life Cycle of your Package Functions
Version: 1.0.4
Version: 1.0.4.9000
Authors@R: c(
person("Lionel", "Henry", , "[email protected]", role = c("aut", "cre")),
person("Hadley", "Wickham", , "[email protected]", role = "aut",
Expand All @@ -23,18 +23,19 @@ Suggests:
covr,
crayon,
knitr,
lintr,
lintr (>= 3.1.0),
rmarkdown,
testthat (>= 3.0.1),
tibble,
tidyverse,
tools,
vctrs,
withr
withr,
xml2
VignetteBuilder:
knitr
Config/Needs/website: tidyverse/tidytemplate, usethis
Config/testthat/edition: 3
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.1
RoxygenNote: 7.2.3
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export(expect_defunct)
export(expect_deprecated)
export(is_present)
export(last_lifecycle_warnings)
export(lifecycle_linter)
export(lint_lifecycle)
export(lint_tidyverse_lifecycle)
export(pkg_lifecycle_statuses)
Expand Down
10 changes: 10 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# lifecycle (development version)

* Improvements to `lint_lifecycle()` and `lint_tidyverse_lifecycle()` (@AshesITR):
* Updated to support lintr >= 3.0.0 (#178).
* Fixed default `pattern=` argument to only find R files (#165).
* Fixed `lint_tidyverse_lifecycle()` ignoring the `pattern=` argument (#179).
* Added support for Quarto qmd files to the default `pattern=` argument (#155).
* Added support for Rnw, Rhtml, Rrst, Rtex and Rtxt files to the default `pattern=` argument.
* Exported `lifecycle_linter()` to allow including the linter in `.lintr` configurations when using `lintr` (#122).

# lifecycle 1.0.4

* Repeated calls to `deprecate_soft()` and `deprecate_warn()` are faster thanks
Expand Down
134 changes: 87 additions & 47 deletions R/lint.R
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ db_function <- function(db) {
#' Include `NA` if you want to include functions without a specified lifecycle
#' status in the results.
#' @export
#' @rdname lint_lifecycle
#' @rdname lifecycle_linter
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)
Expand Down Expand Up @@ -82,67 +82,107 @@ get_usage_function_names <- function(x) {
}
}

#' @rdname lifecycle_linter
#' @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 = "(?i)[.](r|rmd|qmd|rnw|rhtml|rrst|rtex|rtxt)$",
which = c("superseded", "deprecated", "questioning", "defunct", "experimental", "soft-deprecated", "retired"),
symbol_is_undesirable = FALSE
) {
which <- match.arg(which, several.ok = TRUE)

check_installed(c("lintr", "vctrs", "xml2"))

lintr::lint_dir(
path = path,
pattern = pattern,
linters = lifecycle_linter(packages = packages, which = which, symbol_is_undesirable = symbol_is_undesirable)
)
}

#' @rdname lifecycle_linter
#' @export
lint_tidyverse_lifecycle <- function(
path = ".",
pattern = "(?i)[.](r|rmd|qmd|rnw|rhtml|rrst|rtex|rtxt)$",
which = c("superseded", "deprecated", "questioning", "defunct", "experimental", "soft-deprecated", "retired"),
symbol_is_undesirable = FALSE
) {
which <- match.arg(which, several.ok = TRUE)

check_installed(c("lintr", "vctrs", "xml2", "tidyverse"))

lint_lifecycle(
packages = tidyverse::tidyverse_packages(),
pattern = pattern,
path = path,
which = which,
symbol_is_undesirable = symbol_is_undesirable
)
}

#' Lint usages of functions that have a non-stable life cycle.
#'
#' - `lint_lifecycle` dynamically queries the package documentation for packages
#' - `lifecycle_linter()` creates a linter for lifecycle annotations which can be
#' included in a `.lintr` configuration if `lintr` is used directly.
#' - `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`
#' - `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
#' - `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`.
#' @param which Vector of lifecycle statuses to lint.
#' @param symbol_is_undesirable Also lint symbol usages, e.g. `lapply(x, is_na)`?
#'
#' @examples
#'
#' lintr::lint(text = "is_na(x)", linters = lifecycle_linter(packages = "rlang"))
#' lintr::lint(text = "lapply(x, is_na)", linters = lifecycle_linter(packages = "rlang", symbol_is_undesirable = TRUE))
#'
#' @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"))
lifecycle_linter <- function(
packages = tidyverse::tidyverse_packages(),
which = c("superseded", "deprecated", "questioning", "defunct", "experimental", "soft-deprecated", "retired"),
symbol_is_undesirable = FALSE
) {
Copy link
Author

@AshesITR AshesITR Nov 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idea for future expansion: except = character() arg to explicitly allow some functions.

check_installed(c("lintr", "vctrs", "xml2"))

life_cycles <- vctrs::vec_rbind(!!!lapply(packages, pkg_lifecycle_statuses, which = which))
bad_usages <- sprintf("`%s::%s` is %s", life_cycles$package, life_cycles$fun, life_cycles$lifecycle)
names(bad_usages) <- life_cycles$fun

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))
)
}
}
if (symbol_is_undesirable) {
xpath <- sprintf(
"//SYMBOL_FUNCTION_CALL[%1$s] | //SYMBOL[%1$s]",
paste0("text() = '", names(bad_usages), "'", collapse = " or ")
)
} else {
xpath <- sprintf(
"//SYMBOL_FUNCTION_CALL[%s]",
paste0("text() = '", names(bad_usages), "'", collapse = " or ")
)
}

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)
lintr::Linter(function(source_expression) {
if (!lintr::is_lint_level(source_expression, "expression")) {
return(list())
}

check_installed(c("lintr", "vctrs", "tidyverse"))
matched_nodes <- xml2::xml_find_all(source_expression$xml_parsed_content, xpath)
fun_names <- lintr::get_r_string(matched_nodes)

lint_lifecycle(packages = tidyverse::tidyverse_packages(), path = path, which = which)
lintr::xml_nodes_to_lints(
matched_nodes,
source_expression = source_expression,
lint_message = unname(bad_usages[fun_names])
)
})
}
2 changes: 2 additions & 0 deletions inst/lintr/linters.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
linter,tags
lifecycle_linter,robustness best_practices configurable
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes linters_with_tags("best_practices", packages = "lifecycle") work.

6 changes: 3 additions & 3 deletions man/lifecycle-package.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

72 changes: 72 additions & 0 deletions man/lifecycle_linter.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 0 additions & 54 deletions man/lint_lifecycle.Rd

This file was deleted.

Loading