-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
10603cf
commit 8804b66
Showing
5 changed files
with
303 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
defmodule Recode.Task.UnnecessaryIfUnless do | ||
@shortdoc "Removes redundant booleans" | ||
|
||
@moduledoc """ | ||
Redudant booleans make code needlesly verbose. | ||
# preferred | ||
foo == bar | ||
# not preferred | ||
if foo == bar do | ||
true | ||
else | ||
false | ||
end | ||
This task rewrites the code when `mix recode` runs with `autocorrect: true`. | ||
""" | ||
|
||
use Recode.Task, corrector: true, category: :readability | ||
|
||
alias Recode.Issue | ||
alias Recode.Task.UnnecessaryIfUnless | ||
alias Rewrite.Source | ||
alias Sourceror.Zipper | ||
|
||
@impl Recode.Task | ||
def run(source, opts) do | ||
{zipper, issues} = | ||
source | ||
|> Source.get(:quoted) | ||
|> Zipper.zip() | ||
|> Zipper.traverse([], fn zipper, issues -> | ||
remove_unecessary_if_unless(zipper, issues, opts[:autocorrect]) | ||
end) | ||
|
||
case opts[:autocorrect] do | ||
true -> | ||
Source.update(source, UnnecessaryIfUnless, :quoted, Zipper.root(zipper)) | ||
|
||
false -> | ||
Source.add_issues(source, issues) | ||
end | ||
end | ||
|
||
defp remove_unecessary_if_unless( | ||
%Zipper{node: {conditional, meta, body}} = zipper, | ||
issues, | ||
true | ||
) | ||
when conditional in [:if, :unless] do | ||
case extract(body, conditional) do | ||
{:ok, expr} -> | ||
expr = put_leading_comments(expr, meta) | ||
|
||
{Zipper.replace(zipper, expr), issues} | ||
|
||
:error -> | ||
{zipper, issues} | ||
end | ||
end | ||
|
||
defp remove_unecessary_if_unless( | ||
%Zipper{node: {conditional, meta, body}} = zipper, | ||
issues, | ||
false | ||
) | ||
when conditional in [:if, :unless] do | ||
issues = | ||
case extract(body, conditional) do | ||
{:ok, _expr} -> | ||
message = "Avoid unnecessary `if` and `unless`" | ||
issue = Issue.new(UnnecessaryIfUnless, message, meta) | ||
[issue | issues] | ||
|
||
:error -> | ||
issues | ||
end | ||
|
||
{zipper, issues} | ||
end | ||
|
||
defp remove_unecessary_if_unless(zipper, issues, _autocorrect), do: {zipper, issues} | ||
|
||
defp extract( | ||
[ | ||
expr, | ||
[ | ||
{{:__block__, _, [:do]}, {:__block__, _, [left]}}, | ||
{{:__block__, _, [:else]}, {:__block__, _, [right]}} | ||
] | ||
], | ||
conditional | ||
) | ||
when is_boolean(left) and is_boolean(right) do | ||
case {conditional, left, right} do | ||
{:if, true, false} -> {:ok, expr} | ||
{:if, false, true} -> {:ok, {:not, [], [expr]}} | ||
{:unless, true, false} -> {:ok, {:not, [], [expr]}} | ||
{:unless, false, true} -> {:ok, expr} | ||
end | ||
end | ||
|
||
defp extract(_, _), do: :error | ||
|
||
defp put_leading_comments(expr, meta) do | ||
comments = meta[:leading_comments] || [] | ||
Sourceror.append_comments(expr, comments) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
defmodule Recode.Task.UnnecessaryIfUnlessTest do | ||
use RecodeCase | ||
|
||
alias Recode.Task.UnnecessaryIfUnless | ||
|
||
describe "run/1" do | ||
test "equals" do | ||
code = """ | ||
if foo == bar do | ||
true | ||
else | ||
false | ||
end | ||
""" | ||
|
||
expected = """ | ||
foo == bar | ||
""" | ||
|
||
code | ||
|> run_task(UnnecessaryIfUnless, autocorrect: true) | ||
|> assert_code(expected) | ||
end | ||
|
||
test "predicate" do | ||
code = """ | ||
if foo?(bar) do | ||
true | ||
else | ||
false | ||
end | ||
""" | ||
|
||
expected = """ | ||
foo?(bar) | ||
""" | ||
|
||
code | ||
|> run_task(UnnecessaryIfUnless, autocorrect: true) | ||
|> assert_code(expected) | ||
end | ||
|
||
test "keyword syntax" do | ||
code = """ | ||
if foo?(bar), do: true, else: false | ||
""" | ||
|
||
expected = """ | ||
foo?(bar) | ||
""" | ||
|
||
code | ||
|> run_task(UnnecessaryIfUnless, autocorrect: true) | ||
|> assert_code(expected) | ||
end | ||
|
||
test "negates code with reverse booleans" do | ||
code = """ | ||
if some_call?() && (some_other_call?() || another_call?()) do | ||
false | ||
else | ||
true | ||
end | ||
""" | ||
|
||
expected = """ | ||
not (some_call?() && (some_other_call?() || another_call?())) | ||
""" | ||
|
||
code | ||
|> run_task(UnnecessaryIfUnless, autocorrect: true) | ||
|> assert_code(expected) | ||
end | ||
|
||
test "maintains leading comments" do | ||
code = """ | ||
# I'm a comment | ||
if foo?(bar) do | ||
true | ||
else | ||
false | ||
end | ||
""" | ||
|
||
expected = """ | ||
# I'm a comment | ||
foo?(bar) | ||
""" | ||
|
||
code | ||
|> run_task(UnnecessaryIfUnless, autocorrect: true) | ||
|> assert_code(expected) | ||
end | ||
|
||
test "keeps code as is without booleans" do | ||
code = """ | ||
if foo?(bar) do | ||
bar | ||
else | ||
false | ||
end | ||
""" | ||
|
||
expected = """ | ||
if foo?(bar) do | ||
bar | ||
else | ||
false | ||
end | ||
""" | ||
|
||
code | ||
|> run_task(UnnecessaryIfUnless, autocorrect: true) | ||
|> assert_code(expected) | ||
end | ||
|
||
test "works with unless" do | ||
code = """ | ||
unless foo?(bar) do | ||
false | ||
else | ||
true | ||
end | ||
""" | ||
|
||
expected = """ | ||
foo?(bar) | ||
""" | ||
|
||
code | ||
|> run_task(UnnecessaryIfUnless, autocorrect: true) | ||
|> assert_code(expected) | ||
end | ||
|
||
test "negates unless" do | ||
code = """ | ||
unless foo?(bar) do | ||
true | ||
else | ||
false | ||
end | ||
""" | ||
|
||
expected = """ | ||
not foo?(bar) | ||
""" | ||
|
||
code | ||
|> run_task(UnnecessaryIfUnless, autocorrect: true) | ||
|> assert_code(expected) | ||
end | ||
|
||
test "reports no issues" do | ||
""" | ||
foo == bar | ||
""" | ||
|> run_task(UnnecessaryIfUnless, autocorrect: false) | ||
|> refute_issues() | ||
end | ||
|
||
test "reports an issue" do | ||
""" | ||
if foo == bar do | ||
true | ||
else | ||
false | ||
end | ||
""" | ||
|> run_task(UnnecessaryIfUnless, autocorrect: false) | ||
|> assert_issue_with(reporter: UnnecessaryIfUnless) | ||
end | ||
end | ||
end |