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

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

Open
jayqui opened this issue Apr 12, 2024 · 4 comments
Open

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

jayqui opened this issue Apr 12, 2024 · 4 comments

Comments

@jayqui
Copy link

jayqui commented Apr 12, 2024

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.

@jayqui
Copy link
Author

jayqui commented Apr 12, 2024

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

@dvandersluis
Copy link
Member

dvandersluis commented Apr 12, 2024

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.

@dvandersluis
Copy link
Member

dvandersluis commented Apr 12, 2024

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.

@vlad-pisanov
Copy link

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.

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

No branches or pull requests

3 participants