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 #4130] Add a new cop Style/RedundantConditional #4453

Merged
merged 2 commits into from
Aug 21, 2017

Conversation

petehamilton
Copy link
Contributor

@petehamilton petehamilton commented Jun 2, 2017

Returning true/false from a conditional involving a boolean conditional
is unnecessary. Instead it makes more sense to just use the boolean
expression itself.

This cop identifies and autocorrects this in ternary, if/else and
modifier form.

See #4130


Before submitting the PR make sure the following are checked:

  • Wrote good commit messages.
  • Commit message starts with [Fix #issue-number] (if the related issue exists).
  • Used the same coding conventions as the rest of the project.
  • Feature branch is up-to-date with master (if not - rebase it).
  • Squashed related commits together.
  • Added tests.
  • Added an entry to the Changelog if the new code introduces user-observable changes. See changelog entry format.
  • All tests are passing.
  • The new code doesn't generate RuboCop offenses.
  • The PR relates to only one subject with a clear title
    and description in grammatically correct, complete sentences.
  • Updated cop documentation with rake generate_cops_documentation (required only when you've added a new cop or changed the configuration/documentation of an existing cop).

class RedundantTernary < Cop
include ConfigurableEnforcedStyle

OPS = %w[== === != < > <= >= <=>].freeze
Copy link
Collaborator

Choose a reason for hiding this comment

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

There should be a COMPARISON_METHODS constant available to you from Cop, I believe. 🙂

Copy link
Contributor Author

@petehamilton petehamilton Jun 2, 2017

Choose a reason for hiding this comment

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

Ah nice, I took this from the Lint/UselessComparison cop but good to know there's a constant, I can only see it used in Style/YodaCondition but happy to follow that.

def on_if(node)
return unless node.ternary? && offense?(node)

add_offense(node, node.source_range, MSG)
Copy link
Collaborator

Choose a reason for hiding this comment

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

MSG is passed automagically to #add_offense if you omit it. There are also keywords available for the second argument, giving:

add_offense(node, :expression)

Copy link
Contributor Author

@petehamilton petehamilton Jun 2, 2017

Choose a reason for hiding this comment

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

Nice, thanks! Took this from Style/TernaryParentheses but will change.

From a quick look at the code, :expression results in node.loc.expression being used as the range rather than node.source_range but I'm pretty unfamiliar with this code so I'm not sure how they differ. Is there a good place to look in terms of learning more about this part of rubocop?

redundant_ternary?(node) || redundant_ternary_inverted?(node)
end

def autocorrect(node)
Copy link
Collaborator

Choose a reason for hiding this comment

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

There are two #autocorrect methods right now. 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🙈 👍

it_behaves_like "code without offense",
"x == y ? 1 : 10"

xcontext "TODO" do
Copy link
Collaborator

Choose a reason for hiding this comment

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

We don't use code for project tracking. Leave a GitHub issue if you intend to do something later. 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For sure, I was planning to fill a few of these in before merging if possible then move the rest to issues and resolve in followup PRs, this was just my working copy at the time, should have made that clearer, sorry!

@bbatsov
Copy link
Collaborator

bbatsov commented Jun 2, 2017

Btw, probably this cop can easily be extended to cover if/else as well. After all in the AST both if and the ternary op are represented similarly.

@petehamilton petehamilton changed the title Add cop to check for redundant ternary conditions [Fix #4130] Introduce cop for redundant conditional Jun 2, 2017
@petehamilton petehamilton changed the title [Fix #4130] Introduce cop for redundant conditional [Fix #4130] Introduce cop for redundant ternary Jun 2, 2017
@petehamilton petehamilton force-pushed the implement-redundant-ternary branch 2 times, most recently from da5373f to 7f3749c Compare June 2, 2017 21:07
@petehamilton petehamilton changed the title [Fix #4130] Introduce cop for redundant ternary [Fix #4130] Introduce cop for redundant conditions Jun 2, 2017
@petehamilton petehamilton force-pushed the implement-redundant-ternary branch 2 times, most recently from 5fd2463 to f9218b4 Compare June 2, 2017 21:13
@petehamilton
Copy link
Contributor Author

petehamilton commented Jun 2, 2017

@bbatsov @Drenmi have updated this to cover ternary and if/else cases. Would be great to get another review.

Currently I'm planning to leave the following as points for discussion in issues later:

# Generalising to any expression, not just boolean ones
Before: expression ? true : false => expression
 After: expression ? false : true => !expression

# Support negation of boolean logic
Before: x == y
 After: x != y

Before: x == y && a < b ? false : true
 After: x != y || a >=b

# Support boolean method conventions?
Before: foo? ? true : false
 After: foo?

Before: foo? ? false : true
 After: !foo?

# Support assignment of ternaries?
Before: foo = bar? ? baz : nil
 After: foo = baz if bar?

Before: foo = bar? ? nil : baz
 After: foo = baz unless bar?

@petehamilton petehamilton changed the title [Fix #4130] Introduce cop for redundant conditions [Fix #4130] Add a new cop Style/RedundantConditional Jun 2, 2017
@petehamilton petehamilton force-pushed the implement-redundant-ternary branch 5 times, most recently from a0fbdc8 to 369dfae Compare June 2, 2017 21:54
'x == y ? 1 : 10'

it_behaves_like 'code with offense',
"if x == y\n true\nelse\n false\nend",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Happy to change these to be heredocs or similar if preferred.

Copy link
Collaborator

Choose a reason for hiding this comment

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

They would read better for sure.

# x != y
class RedundantConditional < Cop
COMPARISON_OPERATORS = RuboCop::AST::Node::COMPARISON_OPERATORS
.reject { |op| op == :! }.freeze
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think :! has been incorrectly lumped together with comparison operators in the constant in Node. It's okay to have a special case for it in your PR. I'll see if I can surgically remove it at a later point. 🙂

Copy link
Collaborator

Choose a reason for hiding this comment

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

@petehamilton: Please see #4461. 🙂

Copy link
Contributor Author

@petehamilton petehamilton Jun 3, 2017

Choose a reason for hiding this comment

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

🙌 - have updated code to reflect

@petehamilton petehamilton force-pushed the implement-redundant-ternary branch 2 times, most recently from f993e7e to 9113104 Compare June 3, 2017 08:02
class RedundantConditional < Cop
COMPARISON_OPERATORS = RuboCop::AST::Node::COMPARISON_OPERATORS

MSG = 'Returning true/false from a conditional is unnecessary.'.freeze
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd check this to "This conditional expression can be replaced with its condition.". Maybe you can interpolate the condition in the message.

Copy link
Contributor Author

@petehamilton petehamilton Jun 3, 2017

Choose a reason for hiding this comment

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

Done in 503bc9c. I haven't implemented interpolation as I figure we can't guarantee that the conditional will be short and a single-line string, which would presumably result in messy output?

Copy link
Collaborator

Choose a reason for hiding this comment

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

While you're technically speaking true, most conditions are pretty short, so this might not be that big of a deal in practice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fair enough - happy to change.

module RuboCop
module Cop
module Style
# This cop checks for redundant returning of true/false in conditionals
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should end with a ..

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

@petehamilton petehamilton force-pushed the implement-redundant-ternary branch 3 times, most recently from ea0bf94 to e8358cd Compare June 3, 2017 11:23
end
END
"x == y\n",
'x == y'
Copy link
Contributor Author

@petehamilton petehamilton Jun 3, 2017

Choose a reason for hiding this comment

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

@bbatsov not too happy with these specs now, they feel kind of messy with the trailing newline and then a repeat for the message. Any thoughts?

Could assume the message will be the resulting code stripped of any trailing newlines but that's not guaranteed to be true.

Copy link
Collaborator

Choose a reason for hiding this comment

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

There's some room for improvement for sure. I don't care much about the newlines, but I think you can pass some param which you'd interpolate in the it message, so it'd be more apparent which test is testing what. Right now the output would be pretty confusing.

'x == y'

it_behaves_like 'code with offense',
<<-END.strip_indent,
Copy link
Collaborator

Choose a reason for hiding this comment

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

We typically use RUBY instead of END for ruby code snippets.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@petehamilton
Copy link
Contributor Author

petehamilton commented Jun 23, 2017

@bbatsov hey, sorry for taking a little while to come back to this one. Keen to get it over the line.

Right now test output looks like this with -f d (i'm inspecting the code so the newlines don't screw up the output).

> be rspec spec/rubocop/cop/style/redundant_conditional_spec.rb -f d
Run options:
  include {:focus=>true}
  exclude {:broken=>#<Proc:./spec/spec_helper.rb:39>}

All examples were filtered out; ignoring {:focus=>true}

Randomized with seed 14967

RuboCop::Cop::Style::RedundantConditional
  behaves like code with offense
    when checking "if x == y\n  false\nelse\n  true\nend\n"
      auto-corrects
      registers an offense
      claims to auto-correct
  behaves like code without offense
    when checking "if x == y\n  1\nelse\n  2\nend\n"
      does not register an offense
  behaves like code with offense
    when checking "x == y ? true : false"
      claims to auto-correct
      auto-corrects
      registers an offense
  behaves like code with offense
    when checking "if x == y\n  true\nelse\n  false\nend\n"
      auto-corrects
      registers an offense
      claims to auto-correct
  behaves like code with offense
    when checking "x == y ? false : true"
      registers an offense
      claims to auto-correct
      auto-corrects
  behaves like code without offense
    when checking "x == y ? 1 : 10"
      does not register an offense

Finished in 0.10577 seconds (files took 1.23 seconds to load)
14 examples, 0 failures

Randomized with seed 14967

What are you suggesting as an alternative? Providing a description of each code snippet instead of the code inline?

For context, I was just following this as an example but happy to change!

@bbatsov
Copy link
Collaborator

bbatsov commented Jun 30, 2017

Right now test output looks like this with -f d (i'm inspecting the code so the newlines don't screw up the output).

The output is good enough for now. Just squash the commits together, rebase on top of master and get the build to pass.

@petehamilton
Copy link
Contributor Author

@bbatsov on it.

@dobrynin
Copy link

dobrynin commented Aug 8, 2017

Hi, I noticed this issue while using Rubocop as well. What's the status of the PR?

@petehamilton
Copy link
Contributor Author

petehamilton commented Aug 9, 2017

@dobrynin - my bad, I had it nearly ready to go and then started getting test failures after a rebase and totally dropped the ball.

Turned out they were coming from the change in test inspection methods introduced in #4510. All fixed up and hopefully tests should go green now, at which point I think this should be good to merge.

Returning true/false from a conditional involving a boolean conditional
is unnecessary. Instead it makes more sense to just use the boolean
expression itself.

This cop identifies and autocorrects this in ternary and if/else
statements.
@petehamilton
Copy link
Contributor Author

petehamilton commented Aug 9, 2017

@bbatsov @Drenmi all 👍 ? Think this should be good to merge now.

@bbatsov bbatsov merged commit b6303de into rubocop:master Aug 21, 2017
@petehamilton petehamilton deleted the implement-redundant-ternary branch August 21, 2017 08:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants