-
Notifications
You must be signed in to change notification settings - Fork 186
/
Copy pathfunction_argument_linter.R
79 lines (73 loc) · 2.22 KB
/
function_argument_linter.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#' Function argument linter
#'
#' @description
#' Check that arguments with defaults come last in all function declarations,
#' as per the tidyverse design guide.
#'
#' Changing the argument order can be a breaking change. An alternative to changing the argument order
#' is to instead set the default for such arguments to `NULL`.
#'
#' @examples
#' # will produce lints
#' lint(
#' text = "function(y = 1, z = 2, x) {}",
#' linters = function_argument_linter()
#' )
#'
#' lint(
#' text = "function(x, y, z = 1, ..., w) {}",
#' linters = function_argument_linter()
#' )
#'
#' # okay
#' lint(
#' text = "function(x, y = 1, z = 2) {}",
#' linters = function_argument_linter()
#' )
#'
#' lint(
#' text = "function(x, y, w, z = 1, ...) {}",
#' linters = function_argument_linter()
#' )
#'
#' lint(
#' text = "function(y = 1, z = 2, x = NULL) {}",
#' linters = function_argument_linter()
#' )
#'
#' lint(
#' text = "function(x, y, z = 1, ..., w = NULL) {}",
#' linters = function_argument_linter()
#' )
#'
#' @evalRd rd_tags("function_argument_linter")
#' @seealso
#' - [linters] for a complete list of linters available in lintr.
#' - <https://design.tidyverse.org/required-no-defaults.html>
#' @export
function_argument_linter <- function() {
xpath <- "
(//FUNCTION | //OP-LAMBDA)
/following-sibling::EQ_FORMALS[1]
/following-sibling::SYMBOL_FORMALS[
text() != '...'
and not(following-sibling::*[not(self::COMMENT)][1][self::EQ_FORMALS])
]
"
used_in_missing_xpath <- "
text() = following-sibling::expr[last()]//expr[expr/SYMBOL_FUNCTION_CALL[text() = 'missing']]/expr[2]/SYMBOL/text()
"
Linter(linter_level = "expression", supports_exprlist = TRUE, function(source_expression) {
xml <- source_expression$xml_parsed_content
bad_expr <- xml_find_all(xml, xpath)
uses_missing <- xml_find_lgl(bad_expr, used_in_missing_xpath)
missing_note <-
ifelse(uses_missing, " Consider setting the default to NULL and using is.null() instead of using missing()", "")
xml_nodes_to_lints(
bad_expr,
source_expression = source_expression,
lint_message = paste0("Arguments without defaults should come before arguments with defaults.", missing_note),
type = "style"
)
})
}