-
Notifications
You must be signed in to change notification settings - Fork 187
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
Add repeat_linter
for infinite loops
#2107
Conversation
Codecov Report
@@ Coverage Diff @@
## main #2107 +/- ##
=======================================
Coverage 99.65% 99.65%
=======================================
Files 119 120 +1
Lines 5228 5240 +12
=======================================
+ Hits 5210 5222 +12
Misses 18 18
📢 Have feedback on the report? Share it here. |
R/repeat_linter.R
Outdated
@@ -0,0 +1,46 @@ | |||
#' Repeat linter | |||
#' | |||
#' Check that `while` is not used for infinite loops. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wording suggestion here. this could read as "any infinite while loop is detected" which is broader than the actual linter. maybe "simple infinite loops", or just mention while(TRUE) here
R/repeat_linter.R
Outdated
#' @export | ||
repeat_linter <- function() { | ||
xpath <- " | ||
//WHILE[following-sibling::*[1][self::OP-LEFT-PAREN] and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
first condition looks redundant to me. is it possible to write a while loop without '('? if you're worried about 'while' being used in other contexts, while is a reserved word so it will show up as a SYMBOL IINM (worth adding a test for this to be sure)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
while is a reserved word so it will show up as a SYMBOL IINM (worth adding a test for this to be sure)
When I try to create a test for it, the parser always flags an error. I would skip the tests if you don't have a suggestion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works: parse(text = "`while`(TRUE, NULL)")
, but actually resolves as equivalent to while (TRUE) NULL
.
We can actually get into really funky territory here:
`while` <- identity
while(TRUE)
# [1] TRUE
while (FALSE) { }
# Error in while (FALSE) { : unused argument ({
# })
I'm OK to skip tests.
R/repeat_linter.R
Outdated
xpath <- " | ||
//WHILE[following-sibling::*[1][self::OP-LEFT-PAREN] and | ||
following-sibling::*[2][self::expr[NUM_CONST[text()='TRUE'] and count(*)=1]] | ||
and following-sibling::*[3][self::OP-RIGHT-PAREN]]" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is also redundant. if you're worried about more complicated expressions in the condition, your test for NUM_CONST is implicitly handling that -- TRUE would show up at further nesting of in that case.
R/repeat_linter.R
Outdated
repeat_linter <- function() { | ||
xpath <- " | ||
//WHILE[following-sibling::*[1][self::OP-LEFT-PAREN] and | ||
following-sibling::*[2][self::expr[NUM_CONST[text()='TRUE'] and count(*)=1]] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would simplify this as following-sibling::expr[1]/NUM_CONST[...]
the node structure of while() loops is pretty simple in terms of what can parse, so no need to be overly defensive IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I implemented it as suggested but still had to test that an expression existed at all
R/repeat_linter.R
Outdated
lints <- xml_nodes_to_lints( | ||
xml_find_all(xml, xpath), | ||
source_expression = source_expression, | ||
lint_message = "'while (TRUE)' is not recommended for infinite loops. Use 'repeat' instead.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We prefer the actionable bit first (there's an issue about doing so consistently in the tracker somewhere...). here that would be "Use repeat instead of while(TRUE) for infinite loops"
tests/testthat/test-repeat_linter.R
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
more suggested test cases:
# while as a function call
`while`(TRUE)
# TRUE is the expression
while (j < 5) TRUE
# TRUE in a more complicated condition
while (TRUE && j < 5) { ... }
# sensitivity to '{'
while (TRUE) NULL
while (j < 5) NULL
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also should test for lint locations, especially with multiline code and multiple lints in one expression.
{
while (TRUE) {
}
while (TRUE) {
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code looks good and works, thanks! My comments are for polish/completeness.
Understood. I'm always happy about comments that help to improve. |
R/repeat_linter.R
Outdated
#' @export | ||
repeat_linter <- function() { | ||
xpath <- " | ||
//*[(self::WHILE or self::expr[SYMBOL_FUNCTION_CALL[.='`while`']]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
//*
is really troublesome for XPath -- they are evaluated greedily and so this linter will be really expensive on files with a lot of expressions. See https://github.com/r-lib/lintr/wiki/Writing-robust,-performant-linters.
I really think we can ignore the `while`
case unless there's a specific request for it -- that's a really unusual way to write a while
loop. Otherwise, we would usually write this as //WHILE[...] | //SYMBOL_FUNCTION_CALL[text() = '
while']/parent::expr[...]
.
(TIL you can use . = '...'
to check the text on a node btw! Nice find. I still like text() = '...'
for expressiveness, . = '...'
is a little bit harder to reason about)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will ignore the `while`
case.
I misunderstood you I thought you would have wished for this case by mentioning it as a possible test here
@MEO265 FYI, I edited the PR description. "Solves" is not the ideal keyword since it won't auto-close the issue -- see https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue. |
Thanks for the info. |
Can someone tell me why pipeline "check-link-rot" fails and whether I can influence it at all? |
ignore it, it's been happening to other builds as well, that link appears fragile. |
@MichaelChirico I looked at your changes and will try to do the same next time |
PR resolves #2106