-
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
Linter for explicit/implicit returns #2271
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #2271 +/- ##
=======================================
Coverage 99.40% 99.41%
=======================================
Files 123 124 +1
Lines 5590 5640 +50
=======================================
+ Hits 5557 5607 +50
Misses 33 33 ☔ View full report in Codecov by Sentry. |
The warnings stem from exclusions of linters that you removed by providing |
Hmm, we do already have an We have tested ours pretty extensively (no false positives on 10,000+ files) -- here's all the tests we've written: https://gist.github.com/MichaelChirico/9164672a4e762627c09ad6b521e54964 Please ensure these tests pass under Here's our XPath: https://gist.github.com/MichaelChirico/084bc163c01ad0debba8b1b945b75009 The major issue I'm aware of with our implementation is it doesn't handle arbitrary nesting in terminal See #884 for linters we've already written that are just a matter of porting over. |
I agree we shouldn't require |
Most likely, these are from copy-pasting our internal code where the styles are opposite. Yes, we should remove them here for consistency (and because we're meant to enforce the tidyverse guide) |
Just by looking at it I can tell that my linter would produce lots of false positives. Many that one should definitely pay attention to, e.g. Maybe in addition to a fixed number of basic functions that are taken into account in the code, there should be the option to accept more functions (from other packages).
Thanks for access to the code. I see that you have already paid a lot more attention. What is the best way to proceed? I can try to bring the two together and make sure everything passes, although that might take a while (which doesn't bother me), but if you want to take over that would be ok too.
Is there a way to see the code of all linters still to be transferred or to get access to certain ones? If so, I would like to try to support whenever my time allows. |
Thanks for the answer, what surprised me is that not a single warning came when I called up my linter without argument |
Not easily... I have to strip some internal-only pieces from the code before uploading them here. If you'd like to see specific linters I am happy to do so one a one-by-one basis. |
I think I'd be open to simplifying things a bit -- always lint on a terminal foo <- function() {
for (ii in 1:10) {
ii
}
}
x <- foo()
dput(x)
# NULL So under explicit return style, IMO it's always preferable to make that behavior more explicit: foo <- function() {
for (ii in 1:10) {
ii
}
return(invisible())
} |
Yes, that's what I would do... one issue is the list is already a bit long, the signature will get pretty messy if the default list is huge. So I would propose the parameter being extra functions to skip. Something like # User has no control here; all of these come from base
default_allowed_functions <- c(
# Normal calls
"return", "stop", "warning", "message", "stopifnot", "q", "quit",
"invokeRestart", "tryInvokeRestart",
# Functions related to S3 methods
"UseMethod", "NextMethod",
# Functions related to S4 methods
"standardGeneric", "callNextMethod",
# Functions related to C interfaces
".C", ".Call", ".External", ".Fortran"
)
# User supplies these; internally we would use this
extra_allowed_functions <- c(
# Normal calls from non-default libraries
"LOG", "abort",
# tests in the RUnit framework are functions ending with a call to one
# of the below. would rather users just use a different framework
# (e.g. testthat or tinytest), but already 250+ BUILD files depend
# on RUnit, so just cater to that. confirmed the efficiency impact
# of including these is minimal.
# RUnit tests look like 'TestInCamelCase <- function()'
# NB: check for starts-with(text(), 'Test') below is not sufficient, e.g.
# in cases of a "driver" test function taking arguments and the main unit
# test iterating over those.
"checkEquals", "checkEqualsNumeric", "checkException", "checkIdentical",
"checkStop", "checkTrue", "checkWarnings",
) |
I'm assuming Agree we should silently add all base R exceptions and maybe rlang (abort)? |
SGTM, though maybe should add any other signalling calls from rlang as well? not super familiar with what's offered, I think warn()? |
Actually, why allow |
Hmm, not sure we noticed that at the time. I do find it very strange that behavior differs from Anyway, for our case I would still skip tryCatch(
foo(),
error = \(e) {
cat(Sys.time(), file="log")
cat(e$message, file="log", append=TRUE)
warning(e)
}
) Treating all signalling functions the same makes more sense to me. That said, for {lintr}'s defaults, we can consider something different. |
Maybe instead of allowing |
@MichaelChirico I think it would be really useful, even outside of this PR, if we included some of the functions you use for glue, like |
in {lintr} that's xp_text_in_table, see xp_utils.R for similar helpers |
…feature/return_linter
|
@MichaelChirico as I see it, you've already done everything or is there still something to do for me? |
yes, I tried to clear the pending tasks. good for another round of review in case there's anything pending needed for initial merge. |
Okay, I don't have any more comments from my side |
Nice. I'll try to review later this day. |
R/return_linter.R
Outdated
} else { | ||
# See `?.onAttach`; these functions are all exclusively used for their | ||
# side-effects, so implicit return is generally acceptable | ||
default_except <- c(".onLoad", ".onUnload", ".onAttach", ".onDetach", ".Last.lib") |
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.
just noticed we could probably re-use this:
Lines 186 to 194 in 1c36e0d
special_funs <- c( | |
".onLoad", | |
".onAttach", | |
".onUnload", | |
".onDetach", | |
".Last.lib", | |
".First", | |
".Last" | |
) |
* remove incorrect comment in default_linter_testcode.R * fix NEWS entry (argument is called `return_style`) * reuse `special_funs` constant * convert all tests from lines <- c(...) to trim_some()
@MichaelChirico PTAL, I've fixed what I found directly on the branch. |
@@ -20,6 +25,7 @@ f = function (x,y = 1){} | |||
# object_name | |||
# object_usage | |||
# open_curly | |||
# return |
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 inaccurate. return_linter()
doesn't lint the following function.
@@ -25,7 +25,6 @@ g <- function(x) { | |||
# object_name | |||
# object_usage | |||
# open_curly | |||
# return |
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.
oh! in some commit I added an explicit return here. Must not have survived. I see someone (possibly me?) added a return_linter()
test case to this file in another commit.
" FALSE", | ||
"}" | ||
expect_lint( | ||
trim_some(" |
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.
thanks for cleaning these up to use trim_some()
!
Great work everyone and thanks for the patience! 70 comments, that may be a record :) |
Closes #1100.
I have designed the implementation in such a way that as far as possible no false positive errors occur. This leads to a relatively large number of false negatives, but I would like to accept that.
If anyone can construct false positive cases, I would be grateful if you would write them in the comments.
Inline functions have been ignored so far, this is to a certain extent intentional, as I personally am a supporter of explicit returns, but find them unnecessary in inline functions. On the other hand, this case would have become much more complex.
If someone ever wants to implement it, I think an
lint_inline
parameter would be nice.FYI:
There are 10 explicit
returns
(that the linter finds) in thelinter
package and although I prefer them, they should probably be removed for consistency.