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
Added new Lint/TopLevelReturnWithArgument
cop
#8377
Added new Lint/TopLevelReturnWithArgument
cop
#8377
Conversation
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.
Good PR!
A few comments. Biggest issue is that return 42 if condition
, or module X; return 42
won't be caught
MSG = 'Top level return with argument detected.' | ||
|
||
def on_return(return_node) | ||
add_offense(return_node) if ancestors_valid?(return_node) && !return_node.arguments.empty? |
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.
return_node.arguments.empty?
can also be written return_node.arguments?
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.
Aah yes!
def ancestors_valid?(return_node) | ||
return true if return_node.parent.nil? | ||
|
||
if return_node.parent.instance_of?(AST::Node) && return_node.parent.parent.nil? |
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.
Won't return_node.parent.instance_of?(AST::Node)
always be 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.
return_node.parent.is_a?(AST::Node)
will always return true, but return_node.parent.instance_of?(AST::Node)
would be true only if that object is strictly of that exact class, and not the subclass.
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, right, my bad. Then I would say you need to tweak this test; you should not rely on the class of Node
, only on their type
. Here anyways I believe it is not the best anyways; you're excluding if
for example.
return true | ||
end | ||
|
||
false |
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'm surprised there's no cop against if condition; return true; end; return false
=> return condition
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 did write it in one line, but then it exceeded the 100 characters limit, so had to change it to this.
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.
@marcandre There actually is a cop that suggests this, one suggests for single line if and the other suggests to remove redundant false.
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.
In this case here, you can remove the if
, the return true\n end\n false
completely...
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 if condition checks for the types and returns things appropriately, if we remove that, the cop will report false positives.
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.
Updated this! Thanks for the idea. 😄
@marcandre Updated the PR. Now |
RIght, good, but it won't catch I was trying to hint at a more general logic. Unless I'm missing something, you can check all ancestors for either a |
Now this checks for the ancestors to not belong to the |
Much better. Please do not rely on classes ever, as this is subject to change. You should rely on the result of |
Aah, this suggestion and the resulting changes made the code so compact! Can't thank you enough for this. |
|
||
def ancestors_valid?(return_node) | ||
prohibited_ancestors = return_node.each_ancestor(:block, :def, :defs) | ||
return false if prohibited_ancestors.any? |
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.
Personal preference, I'd simply write prohibited_ancestors.none?
, no return, no if...
MSG = 'Top level return with argument detected.' | ||
|
||
def on_return(return_node) | ||
add_offense(return_node) if ancestors_valid?(return_node) && return_node.arguments? |
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'd switch the conditions' order, as arguments?
is very quick while ancestors_valid?
might be slower
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.
Yes, this also makes a lot of sense given Ruby's short circuit evaluation.
Cool, I'm happy you're happy about the resulting code. I have two last remarks for styling/performance... Also you'll have to rebase your branch |
Made performance & styling related changes, and also rebased my branch. 😄 |
This cop works by validating the ancestors of the return node. A | ||
top-level return node's ancestors will never belong to `AST::BlockNode` | ||
or `AST::DefNode` class. |
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'd remove this comment; implementation detail is too technical and not useful to cop users.
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.
Makes sense to me, removed this. Added this inside the class instead.
|
||
[1, 2, 3, 4, 5].each { |n| return n } | ||
|
||
return # Should raise a `top level return with argument detected` offense |
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.
Change comment or remove...
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.
Removed.
it 'Expects no offense from the method-level return statement' do | ||
expect_no_offenses(<<~RUBY) | ||
def method | ||
return 'Hello World' | ||
end | ||
RUBY | ||
end | ||
|
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.
Remove this spec, it is redundant with the next spec.
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.
Done.
it 'Expects an offense from the return with arguments' do | ||
expect_offense(<<~RUBY) | ||
if a == b; warn 'hey'; return 42; end | ||
^^^^^^^^^ Top level return with argument detected. | ||
RUBY | ||
end |
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.
Seems redundant with previous spec
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.
Yes, removed this spec.
it 'Expects no offense from the return without arguments' do | ||
expect_no_offenses(<<~RUBY) | ||
if a == b; warn 'hey'; return; end | ||
RUBY | ||
end | ||
|
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.
Seems redundant with next spec
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.
Yes, removed this.
Implementation is perfect now 🎉. I think some specs can be removed and some doc can be modified. |
# This cop works by validating the ancestors of the return node. A | ||
# top-level return node's ancestors will never belong to `AST::BlockNode` | ||
# or `AST::DefNode` class. | ||
# |
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.
Please update and put inside the class so it doesn't show in the doc
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.
Updated this and added it inside class.
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.
LGTM, I'll let @bbatsov check naming & al.
(Will need to be squash-merged)
Hey @bbatsov, can you please review this PR. 😄 |
It's ok, I probably should have simply merged it, looks really good. Looks like there are conflicts again, could you rebase and I'll merge it? |
@marcandre One test failed erroneously, and I cannot seem to re-run it, can you please re-run it? Once that passes this should be good to merge. |
Thanks @IamRaviTejaG 🎉 |
Follow rubocop#7868, rubocop#8377, and rubocop#8395 This PR uses `Cop::Base` API for `Lint/TopLevelReturnWithArgument`.
Fixes #7425
This cop detects top level return statements with argument(s).
Before submitting the PR make sure the following are checked:
[Fix #issue-number]
(if the related issue exists).master
(if not - rebase it).bundle exec rake default
. It executes all tests and RuboCop for itself, and generates the documentation.