rubocop / rubocop

A Ruby static code analyzer and formatter, based on the community Ruby style guide.

Home Page:https://docs.rubocop.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Cop(s) Idea: remove redundant booleans from control flow

jayqui opened this issue · comments

Is your feature request related to a problem? Please describe.

Basic problem & use case

It is possible to run across cases where there are booleans in the antecedents of conditionals (true ? foo : bar). This kind of case can be simplified to return the logical branch that the statement will always results in (foo). A cop that could take care of this might be called Lint/BooleanCondition.

Perhaps sometimes a well-intentioned developer could straightforwardly make this kind of mistake. Anyway, another use case in production code is with what my team calls "fully-enabled feature flags": a bit of conditional code that will forever more evaluate to true, which we can reasonably gsub to just true. If we had Lint/BooleanCondition cop, then we could automate the feature flags away:

# original
feature_flags(:foo_flag_enabled, user) ? render(:new_page) : render(:legacy_page)

# substitute `true` for fully enabled flag
true ? render(:new_page) : render(:legacy_page)

# apply `Lint/BooleanCondition`
render(:new_page)

Similar cases

antecendents that always evaluate to true

Sometimes a conditional can have an antecedent that will always evaluate to true, especially when it contains no variables, e.g. 2 > 1 ? foo : bar, which will always evaluate to foo. Perhaps that kind of case could also be taken care of by a Lint/BooleanCondition cop.

conjunctions and disjunctions

true && foo will always evaluate to foo. true || foo will always evaluate to true. Perhaps such cases should also fit into the scope of Lint/BooleanCondition. If not, then those cases could have their own cops, Lint/BooleanInConjunction and Lint/BooleanInDisjucntion.

unless, case, etc.

Perhaps it goes without saying, but we would want Lint/BooleanCondition to handle the various kinds of conditionals Ruby allows for, including if, the ternary operator, unless, case, and possibly and and or.

Describe the solution you'd like

A solution that could auto-correct away such cases as I will post in the first comment.

1. Boolean as antecedent of a conditional (Lint/BooleanCondition)

1.1 if/else with true

# Bad
true ? foo : bar

# Bad
if true
  foo
elsif anything
  bar
else
  "never going to get here"
end

# Good 
foo

1.2 if/else with false

# Bad
false ? foo : bar

# Bad
if false
  foo
else
  bar
end

# Good
bar

1.3 if/elsif/else with false as first antecedent

# Bad
if false
  "a" # never going to get here
elsif bar
  "b"
else
  "c"
end

# Good
if bar
  "b"
else
  "c"
end

1.4 if/elsif/else with false as an elsif antecedent

# Bad
if foo
  "a"
elsif false
  "b" # never going to get here
else
  "c"
end

# Good
if foo
  "a"
else
  "c"
end

1.5 if/elsif/else with true as an elsif antecedent

# Bad
if foo
  "a"
elsif true
  "b" 
else
  "c" # never going to get here
end

# Bad
case
when foo then "a"
when true then "b"
else "c" # never going to get here
end

# Good
if foo
  "a"
else
  "b"
end

# Good
case
when foo then "a"
else "b"
end

2. Boolean within conjunction (Lint/BooleanInConjunction)

2.1 true as leading conjunct

# Bad
true && "anything"

# Good
true

2.2 true as following conjunct

# Bad
"anything" && true

# Good
!!"anything"

2.3 false as leading conjunct

# Bad
false && "anything"

# Good
false

2.4 false as following conjunct

# Bad
"anything" && false

# Good
false

2.5 true as middle conjunct

# Bad
foo && true && bar

# Good
foo && bar

2.6 false as middle conjunct

# Bad
foo && false && bar

# Good
false

3. Boolean within disjucntion (Lint/BooleanInDisjucntion)

3.1 true as leading disjunct

# Bad
true || "anything"

# Good
true

3.2 true as following disjunct

# Bad
"anything" || true

# Good
true

3.3 false as leading disjunct

# Bad
false || "anything"

# Good
"anything"

3.4 false as following disjunct

# Bad
"anything" || false

# Good
"anything"

3.5 true as middle disjunct

# Bad
foo || true || bar

# Good
true

3.6 false as middle disjunct

# Bad
foo || false || bar

# Good
foo || bar

Note two of your examples are not strictly equivalent (assuming you don't literally mean a string "anything", but rather allow for a method anything):

# Bad
anything || true

# Good
true

In the first case anything will be called and could have side effects. In the second case, it would not be called.

# Bad
foo || true || bar

# Good
true

In the first case foo will be called (bar will not be so is redundant due to short-circuiting). In the second case, neither would be.

2 > 1 ? foo : bar

Although this is easy to see that it is always true, it is not easy to detect via static analysis as the clause (or any other code) is not evaluated and there are many forms it could take.

Sounds like an extension of Lint/LiteralAsCondition which already covers many of these cases:

false ? foo : bar
# W: Lint/LiteralAsCondition: Literal false appeared as a condition.

if false
  "a"
elsif bar
  "b"
else
  "c"
end
# W: Lint/LiteralAsCondition: Literal false appeared as a condition.