-
Notifications
You must be signed in to change notification settings - Fork 186
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
New linter for assignments within conditional expressions #1777
Comments
Personally I use these a lot, and am a big fan, but agree their usage should be pretty limited. There are two cases that come to mind when they are usually a good idea:
Unfortunately, my own opinion doesn't mean much here :) Because this is explicitly called out as bad usage in the style guide, I believe we should offer a linter and make it a default: |
Thanks for sharing your use cases! Very helpful to clarify my thinking about this issue. For the second use case, wouldn't it be better to do something like As for the first case, I totally see the utility of doing this! And, of course, unlike C++ and Java, R doesn't have block scope, and so I thought of this issue. But, as you noted, these can be nolint-ed, and the default should be what the style guide is recommending. |
Great citation, I agree with it completely. |
Some testthat functions maybe: # To test message and output, store results to a variable
expect_warning(out <- f(-1), "already negative")
expect_equal(out, -1) |
Great suggestions! Thanks. I guess we should include most of the functions included here. |
NB the preferred way of using these expectations is to reuse the return value, i.e. nest / pipe expectations, so I would suggest to not suppress these lints by default. expect_warning(expect_equal(log(-1), NaN))
expect_equal(log(-1), NaN) |>
expect_warning() However, the following usages come to my mind: quote(a <- 1)
bquote(a <- 1)
expression(a <- 1)
test_that("my test", {
a <- 1
expect_equal(a, 1)
})
x <- local({
a <- 1
2 * a
}) |
Nice, thanks! These are a lot of exceptions to handle. Here is what I was thinking: We can add a parameter to this linter called We can also create a new function called # default
implicit_assignment_linter(exceptions = default_implicit_assignment_exceptions())
# custom
implicit_assignment_linter(exceptions = c(default_implicit_assignment_exceptions(), "f", "g"))
implicit_assignment_linter(exceptions = c("foo1", "foo2"))
... |
The parameter should be called #' @param except Character vector of functions to be excluded from linting. Not sure about |
This is what it looks like right now. And we haven't even included anything from except = c(
"bquote", "capture.output", "expression",
"expect_error", "expect_warning", "expect_message", "expect_condition",
"expect_no_error", "expect_no_warning", "expect_no_message", "expect_no_condition",
"expect_invisible", "expect_visible", "expect_output", "expect_silent",
"local", "quote", "test_that"
) |
I'm arguing for removing |
IIRC expect_warning() now returns the warning object instead of the expression output, so expect_warning would need such an exception; ditto expect_silent() |
Still not 100% sure this necessitates excepting e.g. even in case the value needs to be re-used in a subsequent test, you can do # bad
expect_warning(prepared_thing <- my_setup_with_warning(), "setup warning")
expect_equal(funny_function(prepared_thing), funny_value) |>
expect_warning("funny warning")
# good
my_setup_with_warning() |>
funny_function() |>
expect_equal(funny_value) |>
expect_warning("setup warning") |>
expect_warning("funny warning") This is also how we structure our tests that expect warnings. |
But even #' # To test message and output, store results to a variable
#' expect_warning(out <- f(-1), "already negative") And, even in our test suite, we do the same: lintr/tests/testthat/test-xml_nodes_to_lints.R Lines 103 to 110 in a41b065
|
Okay, I'm convinced 😇 |
TBH, I agree with what you consider to be good style to write these tests. But, as this is a default linter and the "bad" style in question seems to be acceptable by style guide and widely used, we better support it; otherwise, users might be surprised by the discrepancy between lintr and the style guide. |
* Implement `implicit_assignment_linter()` Closes #1777 * remove new lints * exception for functions that capture side-effect * link to style guide * description * get rid of false positives for control flows * add failing test * Update XPath * expand on the exceptions (to be revised) * more tests * order by package * fix test * more rlang * tests for lambdas * reformat vector * also cover `for` loop * remove lint
In the wild, I have come across cases where variables are assigned within conditional expressions and their value is then immediately used as a condition and/or inside their scope. I think this makes the code difficult to understand, and we should lint it.
WDYT?
Here is a reprex with a simple assignment, but you can imagine far more complex assignments and the difficulty to understand the code increases with the complexity of the assignment.
Created on 2022-11-25 with reprex v2.0.2
The suggested alternative here in the lint message would be the following (i.e. do variable assignment outside the expression and then use the variable instead):
The text was updated successfully, but these errors were encountered: