Skip to content

Commit 560d296

Browse files
authored
Merge branch 'main' into sb-eq
2 parents c9c47cc + c6dab82 commit 560d296

File tree

8 files changed

+438
-85
lines changed

8 files changed

+438
-85
lines changed

.github/workflows/pkgdown.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939

4040
- name: Deploy to GitHub pages 🚀
4141
if: github.event_name != 'pull_request'
42-
uses: JamesIves/github-pages-deploy-action@v4.4.3
42+
uses: JamesIves/github-pages-deploy-action@v4.5.0
4343
with:
4444
clean: false
4545
branch: gh-pages

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ importFrom(utils,tail)
190190
importFrom(utils,txtProgressBar)
191191
importFrom(xml2,as_list)
192192
importFrom(xml2,xml_attr)
193+
importFrom(xml2,xml_children)
193194
importFrom(xml2,xml_find_all)
194195
importFrom(xml2,xml_find_chr)
195196
importFrom(xml2,xml_find_first)

R/T_and_F_symbol_linter.R

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#' `T` and `F` symbol linter
22
#'
3-
#' Avoid the symbols `T` and `F`, and use `TRUE` and `FALSE` instead.
3+
#' Although they can be synonyms, avoid the symbols `T` and `F`, and use `TRUE` and `FALSE`, respectively, instead.
4+
#' `T` and `F` are not reserved keywords and can be assigned to any other values.
45
#'
56
#' @examples
67
#' # will produce lints

R/keyword_quote_linter.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ keyword_quote_linter <- function() {
114114
invalid_assignment_quoting <- is_valid_r_name(get_r_string(assignment_expr))
115115
# NB: XPath is such that there is exactly 1 node per match, making xml_children() ideal.
116116
# xml_child() gets it wrong for 0 (an error) and >1 match.
117-
assignment_to_string <- xml_name(xml2::xml_children(assignment_expr)) == "STR_CONST"
117+
assignment_to_string <- xml_name(xml_children(assignment_expr)) == "STR_CONST"
118118

119119
string_assignment_lints <- xml_nodes_to_lints(
120120
assignment_expr[assignment_to_string & !invalid_assignment_quoting],

R/lintr-package.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
#' @importFrom utils capture.output getParseData getTxtProgressBar globalVariables head relist
1616
#' setTxtProgressBar tail txtProgressBar
1717
#' @importFrom xml2 as_list
18-
#' xml_attr xml_find_all xml_find_chr xml_find_lgl xml_find_num xml_find_first xml_name xml_text
18+
#' xml_attr xml_children xml_find_all xml_find_chr xml_find_lgl xml_find_num xml_find_first xml_name xml_text
1919
#' @rawNamespace
2020
#' if (getRversion() >= "4.0.0") {
2121
#' importFrom(tools, R_user_dir)

R/return_linter.R

Lines changed: 56 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -60,21 +60,15 @@ return_linter <- function(
6060
return_style <- match.arg(return_style)
6161

6262
if (return_style == "implicit") {
63-
xpath <- "
64-
(//FUNCTION | //OP-LAMBDA)
65-
/following-sibling::expr[1][*[1][self::OP-LEFT-BRACE]]
66-
/expr[last()][
67-
expr[1][
68-
not(OP-DOLLAR or OP-AT)
69-
and SYMBOL_FUNCTION_CALL[text() = 'return']
70-
]
71-
]
72-
"
73-
msg <- "Use implicit return behavior; explicit return() is not needed."
63+
body_xpath <- "(//FUNCTION | //OP-LAMBDA)/following-sibling::expr[1]"
64+
# nolint next: object_usage. False positive from {codetools} says 'params' isn't used.
65+
params <- list(
66+
implicit = TRUE,
67+
type = "style",
68+
lint_xpath = "SYMBOL_FUNCTION_CALL[text() = 'return']",
69+
lint_message = "Use implicit return behavior; explicit return() is not needed."
70+
)
7471
} else {
75-
# See `?.onAttach`; these functions are all exclusively used for their
76-
# side-effects, so implicit return is generally acceptable
77-
7872
except <- union(special_funs, except)
7973

8074
base_return_functions <- c(
@@ -94,80 +88,67 @@ return_linter <- function(
9488

9589
return_functions <- union(base_return_functions, return_functions)
9690

97-
control_calls <- c("IF", "FOR", "WHILE", "REPEAT")
98-
99-
# from top, look for a FUNCTION definition that uses { (one-line
100-
# function definitions are excepted), then look for failure to find
101-
# return() on the last() expr of the function definition.
102-
# exempt .onLoad which shows up in the tree like
103-
# <expr><expr><SYMBOL>.onLoad</></><LEFT_ASSIGN></><expr><FUNCTION>...
104-
# simple final expression (no control flow) must be
105-
# <expr><expr> CALL( <expr> ) </expr></expr>
106-
# NB: if this syntax _isn't_ used, the node may not be <expr>, hence
107-
# the use of /*[...] below and self::expr here. position() = 1 is
108-
# needed to guard against a few other cases.
109-
# We also need to make sure that this expression isn't followed by a pipe
110-
# symbol, which would indicate that we need to also check the last
111-
# expression.
112-
# pipe expressions are like
113-
# ...
114-
# <SPECIAL>%&gt;%</SPECIAL>
115-
# <expr><expr><SYMBOL_FUNCTION_CALL>return</SYMBOL_FUNCTION_CALL>
116-
# </expr></expr>
117-
# Unlike the following case, the return should be the last expression in
118-
# the sequence.
119-
# conditional expressions are like
120-
# <expr><IF> ( <expr> ) <expr> [ <ELSE> <expr>] </expr>
121-
# we require _any_ call to return() in either of the latter two <expr>, i.e.,
122-
# we don't apply recursive logic to check every branch, only that the
123-
# two top level branches have at least two return()s
124-
# because of special 'in' syntax for 'for' loops, the condition is
125-
# tagged differently than for 'if'/'while' conditions (simple PAREN)
126-
xpath <- glue("
91+
body_xpath <- glue("
12792
(//FUNCTION | //OP-LAMBDA)[parent::expr[not(
12893
preceding-sibling::expr[SYMBOL[{ xp_text_in_table(except) }]]
12994
)]]
13095
/following-sibling::expr[OP-LEFT-BRACE and expr[last()]/@line1 != @line1]
13196
/expr[last()]
132-
/*[
133-
(
134-
position() = 1
135-
and (
136-
(
137-
{ xp_or(paste0('self::', setdiff(control_calls, 'IF'))) }
138-
) or (
139-
not({ xp_or(paste0('self::', control_calls)) })
140-
and not(
141-
following-sibling::PIPE
142-
or following-sibling::SPECIAL[text() = '%>%']
143-
)
144-
and not(self::expr/SYMBOL_FUNCTION_CALL[
145-
{ xp_text_in_table(return_functions) }
146-
])
147-
)
148-
)
149-
) or (
150-
preceding-sibling::IF
151-
and self::expr
152-
and position() > 4
153-
and not(.//SYMBOL_FUNCTION_CALL[{ xp_text_in_table(return_functions) }])
154-
)
155-
]
15697
")
157-
msg <- "All functions must have an explicit return()."
98+
params <- list(
99+
implicit = FALSE,
100+
type = "warning",
101+
lint_xpath = glue("self::*[not(
102+
(self::expr | following-sibling::SPECIAL[text() = '%>%']/following-sibling::expr/expr[1])
103+
/SYMBOL_FUNCTION_CALL[{ xp_text_in_table(return_functions) }]
104+
)]"),
105+
lint_message = "All functions must have an explicit return()."
106+
)
158107
}
159108

160109
Linter(linter_level = "expression", function(source_expression) {
161110
xml <- source_expression$xml_parsed_content
162111
if (is.null(xml)) return(list())
163112

164-
xml_nodes <- xml_find_all(xml, xpath)
113+
body_expr <- xml_find_all(xml, body_xpath)
114+
115+
params$source_expression <- source_expression
116+
# nested_return_lints not "vectorized" due to xml_children()
117+
lapply(body_expr, nested_return_lints, params)
118+
})
119+
}
120+
121+
nested_return_lints <- function(expr, params) {
122+
child_expr <- xml_children(expr)
123+
if (length(child_expr) == 0L) {
124+
return(list())
125+
}
126+
child_node <- xml_name(child_expr)
165127

128+
if (child_node[1L] == "OP-LEFT-BRACE") {
129+
expr_idx <- which(child_node %in% c("expr", "equal_assign", "expr_or_assign_or_help"))
130+
if (length(expr_idx) == 0L) { # empty brace expression {}
131+
if (params$implicit) {
132+
return(list())
133+
} else {
134+
return(list(xml_nodes_to_lints(
135+
expr,
136+
source_expression = params$source_expression,
137+
lint_message = params$lint_message,
138+
type = params$type
139+
)))
140+
}
141+
}
142+
nested_return_lints(child_expr[[tail(expr_idx, 1L)]], params)
143+
} else if (child_node[1L] == "IF") {
144+
expr_idx <- which(child_node %in% c("expr", "equal_assign", "expr_or_assign_or_help"))
145+
lapply(child_expr[expr_idx[-1L]], nested_return_lints, params)
146+
} else {
166147
xml_nodes_to_lints(
167-
xml_nodes,
168-
source_expression = source_expression,
169-
lint_message = msg,
170-
type = "style"
148+
xml_find_first(child_expr[[1L]], params$lint_xpath),
149+
source_expression = params$source_expression,
150+
lint_message = params$lint_message,
151+
type = params$type
171152
)
172-
})
153+
}
173154
}

man/T_and_F_symbol_linter.Rd

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)