Skip to content
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

[Fix #9429] Fix Style/NegatedIfElseCondition autocorrect to keep comments in correct branch #9436

Merged
merged 1 commit into from Mar 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/fix_comments_negated_if_else_condition_cop.md
@@ -0,0 +1 @@
* [#9429](https://github.com/rubocop-hq/rubocop/issues/9429): Fix `Style/NegatedIfElseCondition` autocorrect to keep comments in correct branch. ([@tejasbubane][])
17 changes: 15 additions & 2 deletions lib/rubocop/cop/style/negated_if_else_condition.rb
Expand Up @@ -29,6 +29,7 @@ module Style
#
class NegatedIfElseCondition < Base
include RangeHelp
include CommentsHelp
extend AutoCorrector

MSG = 'Invert the negated condition and swap the %<type>s branches.'
Expand Down Expand Up @@ -96,10 +97,22 @@ def swap_branches(corrector, node)
if node.if_branch.nil?
corrector.remove(range_by_whole_lines(node.loc.else, include_final_newline: true))
else
corrector.replace(node.if_branch, node.else_branch.source)
corrector.replace(node.else_branch, node.if_branch.source)
if_range = node_with_comments(node.if_branch)
else_range = node_with_comments(node.else_branch)

corrector.replace(if_range, else_range.source)
corrector.replace(else_range, if_range.source)
end
end

def node_with_comments(node)
first_statement = node.begin_type? ? node.children[0] : node
return node if processed_source.ast_with_comments[first_statement].empty?

begin_pos = source_range_with_comment(first_statement).begin_pos
end_pos = node.source_range.end_pos
Parser::Source::Range.new(buffer, begin_pos, end_pos)
end
end
end
end
Expand Down
58 changes: 58 additions & 0 deletions spec/rubocop/cop/style/negated_if_else_condition_spec.rb
Expand Up @@ -129,6 +129,64 @@
RUBY
end

it 'moves comments to correct branches during autocorrect' do
expect_offense(<<~RUBY)
if !condition.nil?
^^^^^^^^^^^^^^^^^^ Invert the negated condition and swap the if-else branches.
# part B
# and foo is 39
foo = 39
koic marked this conversation as resolved.
Show resolved Hide resolved
else
# part A
# and foo is 42
foo = 42
end
RUBY

expect_correction(<<~RUBY)
if condition.nil?
# part A
# and foo is 42
foo = 42
else
# part B
# and foo is 39
foo = 39
end
RUBY
end

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the fixing. There's one more thing. Can you add and pass the following test case ending in comments? (I think it's an edge case 😅 )

  it 'registers and corrects an offense when using negated condition and the end of branches are comment' do
     expect_offense(<<~RUBY)
       if !condition.nil?
       ^^^^^^^^^^^^^^^^^^ Invert the negated condition and swap the if-else branches.
         # part B
         # and foo is 39
         foo = 39
         # end of `then` body comment
       else
         # part A
         # and foo is 42
         foo = 42
         # end of `else` body comment
       end
     RUBY

     expect_correction(<<~RUBY)
       if condition.nil?
         # part A
         # and foo is 42
         foo = 42
         # end of `else` body comment
       else
         # part B
         # and foo is 39
         foo = 39
         # end of `then` body comment
       end
     RUBY
   end

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tejasbubane Can you fix this issue with this PR? I think it's an edge case, so I think it can be merged and improved even at present if you don't have time for this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@koic I tried handling this case but seems like this needs to be fixed at parser level. Below is output of processed_source.ast_with_comments. Note the comment just above else if taken as part of else statement. Is there any other rubocop API that I can use and I am unaware of?

irb(#<RuboCop::Cop::Style::NegatedIfElseCondition:0x00005588841deb30>):007:0> pp processed_source.ast_with_comments
{s(:lvasgn, :foo,
  s(:int, 39))=>
  [#<Parser::Source::Comment (string):2:3 "# part B">,
   #<Parser::Source::Comment (string):3:3 "# and foo is 39">],
 s(:lvasgn, :foo,
  s(:int, 42))=>
  [#<Parser::Source::Comment (string):5:3 "# end of `then` body comment">,
   #<Parser::Source::Comment (string):7:3 "# part A">,
   #<Parser::Source::Comment (string):8:3 "# and foo is 42">],
 s(:if,
  s(:send,
    s(:send,
      s(:send, nil, :condition), :nil?), :!),
  s(:lvasgn, :foo,
    s(:int, 39)),
  s(:lvasgn, :foo,
    s(:int, 42)))=>
  [#<Parser::Source::Comment (string):10:3 "# end of `else` body comment">]}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for investigating. This issue may require deeper investigation to be resolved. And this PR has improved auto-correction, so let's merge it with the current status. Thank you again!

it 'works with comments and multiple statements' do
expect_offense(<<~RUBY)
if !condition.nil?
^^^^^^^^^^^^^^^^^^ Invert the negated condition and swap the if-else branches.
# part A
# and foo is 1 and bar is 2
foo = 1
bar = 2
else
# part B
# and foo is 3 and bar is 4
foo = 3
bar = 4
end
RUBY

expect_correction(<<~RUBY)
if condition.nil?
# part B
# and foo is 3 and bar is 4
foo = 3
bar = 4
else
# part A
# and foo is 1 and bar is 2
foo = 1
bar = 2
end
RUBY
end

it 'does not register an offense when negating condition for `if-elsif`' do
expect_no_offenses(<<~RUBY)
if !x
Expand Down