Repository: rrrene/credo
Pull request #555 (2018-06-12 02:50:48)

Add warning for Enum.map/2 |> Enum.map/2 to prefer one Enum.map/2, as well as all permutations of filter/reject for same concept.
#551 and #552 can use this new helper, too, as they have the same shape. 


Patch:
diff --git a/.credo.exs b/.credo.exs
index 38ba5d73..0ddea2f8 100644
--- a/.credo.exs
+++ b/.credo.exs
@@ -104,17 +104,22 @@
         #
         ## Refactoring Opportunities
         #
-        {Credo.Check.Refactor.DoubleBooleanNegation},
         {Credo.Check.Refactor.CondStatements},
         {Credo.Check.Refactor.CyclomaticComplexity},
+        {Credo.Check.Refactor.DoubleBooleanNegation},
+        {Credo.Check.Refactor.FilterFilter},
+        {Credo.Check.Refactor.FilterReject},
         {Credo.Check.Refactor.FunctionArity},
         {Credo.Check.Refactor.LongQuoteBlocks},
+        {Credo.Check.Refactor.MapMap},
         {Credo.Check.Refactor.MatchInCondition},
         {Credo.Check.Refactor.NegatedConditionsInUnless},
         {Credo.Check.Refactor.NegatedConditionsWithElse},
         {Credo.Check.Refactor.Nesting},
         {Credo.Check.Refactor.PipeChainStart,
          excluded_argument_types: [:atom, :binary, :fn, :keyword], excluded_functions: []},
+        {Credo.Check.Refactor.RejectFilter},
+        {Credo.Check.Refactor.RejectReject},
         {Credo.Check.Refactor.UnlessWithElse},
 
         #
diff --git a/lib/credo/check/refactor/enum_helpers.ex b/lib/credo/check/refactor/enum_helpers.ex
new file mode 100644
index 00000000..026e16e1
--- /dev/null
+++ b/lib/credo/check/refactor/enum_helpers.ex
@@ -0,0 +1,91 @@
+defmodule EnumHelpers do
+  def traverse(
+        ast =
+          {{:., _, [{:__aliases__, meta, [:Enum]}, second]}, _,
+           [{{:., _, [{:__aliases__, _, [:Enum]}, first]}, _, _}, _]},
+        issues,
+        issue_meta,
+        message,
+        trigger,
+        first,
+        second,
+        module
+      ) do
+    new_issue = issue_for(issue_meta, meta[:line], message, trigger, module)
+    {ast, issues ++ List.wrap(new_issue)}
+  end
+
+  def traverse(
+        ast =
+          {:|>, meta,
+           [
+             {{:., _, [{:__aliases__, _, [:Enum]}, first]}, _, _},
+             {{:., _, [{:__aliases__, _, [:Enum]}, second]}, _, _}
+           ]},
+        issues,
+        issue_meta,
+        message,
+        trigger,
+        first,
+        second,
+        module
+      ) do
+    new_issue = issue_for(issue_meta, meta[:line], message, trigger, module)
+    {ast, issues ++ List.wrap(new_issue)}
+  end
+
+  def traverse(
+        ast =
+          {{:., meta, [{:__aliases__, _, [:Enum]}, second]}, _,
+           [
+             {:|>, _, [_, {{:., _, [{:__aliases__, _, [:Enum]}, first]}, _, _}]},
+             _
+           ]},
+        issues,
+        issue_meta,
+        message,
+        trigger,
+        first,
+        second,
+        module
+      ) do
+    new_issue = issue_for(issue_meta, meta[:line], message, trigger, module)
+    {ast, issues ++ List.wrap(new_issue)}
+  end
+
+  def traverse(
+        ast =
+          {:|>, meta,
+           [
+             {:|>, _,
+              [
+                _,
+                {{:., _, [{:__aliases__, _, [:Enum]}, first]}, _, _}
+              ]},
+             {{:., _, [{:__aliases__, _, [:Enum]}, second]}, _, _}
+           ]},
+        issues,
+        issue_meta,
+        message,
+        trigger,
+        first,
+        second,
+        module
+      ) do
+    new_issue = issue_for(issue_meta, meta[:line], message, trigger, module)
+    {ast, issues ++ List.wrap(new_issue)}
+  end
+
+  def traverse(ast, issues, _issue_meta, _message, _trigger, _first, _second, _module) do
+    {ast, issues}
+  end
+
+  defp issue_for(issue_meta, line_no, message, trigger, module) do
+    module.format_issue(
+      issue_meta,
+      message: message,
+      trigger: trigger,
+      line_no: line_no
+    )
+  end
+end
diff --git a/lib/credo/check/refactor/filter_filter.ex b/lib/credo/check/refactor/filter_filter.ex
new file mode 100644
index 00000000..6a3f9ad6
--- /dev/null
+++ b/lib/credo/check/refactor/filter_filter.ex
@@ -0,0 +1,38 @@
+defmodule Credo.Check.Refactor.FilterFilter do
+  @moduledoc """
+  One `Enum.filter/2` is more efficient than `Enum.filter/2 |> Enum.filter/2`.
+
+  This should be refactored:
+
+      ["a", "b", "c"]
+      |> Enum.filter(&String.contains?(&1, "x"))
+      |> Enum.filter(&String.contains?(&1, "a"))
+
+  to look like this:
+
+      Enum.filter(["a", "b", "c"], fn letter ->
+        String.contains?(letter, "x") && String.contains?(letter, "a")
+      end)
+
+  The reason for this is performance, because the two separate calls
+  to `Enum.filter/2` require two iterations whereas doing the
+  functions in the single `Enum.filter/2` only requires one.
+  """
+
+  @explanation [check: @moduledoc]
+
+  use Credo.Check, base_priority: :high
+
+  @doc false
+  def run(source_file, params \\ []) do
+    issue_meta = IssueMeta.for(source_file, params)
+
+    message = "One `Enum.filter/2` is more efficient than `Enum.filter/2 |> Enum.filter/2`"
+    trigger = "filter_filter"
+
+    Credo.Code.prewalk(
+      source_file,
+      &EnumHelpers.traverse(&1, &2, issue_meta, message, trigger, :filter, :filter, __MODULE__)
+    )
+  end
+end
diff --git a/lib/credo/check/refactor/filter_reject.ex b/lib/credo/check/refactor/filter_reject.ex
new file mode 100644
index 00000000..b732ca39
--- /dev/null
+++ b/lib/credo/check/refactor/filter_reject.ex
@@ -0,0 +1,38 @@
+defmodule Credo.Check.Refactor.FilterReject do
+  @moduledoc """
+  One `Enum.filter/2` is more efficient than `Enum.filter/2 |> Enum.reject/2`.
+
+  This should be refactored:
+
+      ["a", "b", "c"]
+      |> Enum.filter(&String.contains?(&1, "x"))
+      |> Enum.reject(&String.contains?(&1, "a"))
+
+  to look like this:
+
+      Enum.filter(["a", "b", "c"], fn letter ->
+        String.contains?(letter, "x") && !String.contains?(letter, "a")
+      end)
+
+  The reason for this is performance, because the two calls to
+  `Enum.reject/2` and `Enum.filter/2` require two iterations whereas
+  doing the functions in the single `Enum.filter/2` only requires one.
+  """
+
+  @explanation [check: @moduledoc]
+
+  use Credo.Check, base_priority: :high
+
+  @doc false
+  def run(source_file, params \\ []) do
+    issue_meta = IssueMeta.for(source_file, params)
+
+    message = "One `Enum.filter/2` is more efficient than `Enum.filter/2 |> Enum.reject/2`"
+    trigger = "filter_reject"
+
+    Credo.Code.prewalk(
+      source_file,
+      &EnumHelpers.traverse(&1, &2, issue_meta, message, trigger, :filter, :reject, __MODULE__)
+    )
+  end
+end
diff --git a/lib/credo/check/refactor/map_map.ex b/lib/credo/check/refactor/map_map.ex
new file mode 100644
index 00000000..d2b5d5cb
--- /dev/null
+++ b/lib/credo/check/refactor/map_map.ex
@@ -0,0 +1,40 @@
+defmodule Credo.Check.Refactor.MapMap do
+  @moduledoc """
+  One `Enum.map/2` is more efficient than `Enum.map/2 |> Enum.map/2`.
+
+  This should be refactored:
+
+      [:a, :b, :c]
+      |> Enum.map(&inspect/1)
+      |> Enum.map(&String.upcase/1)
+
+  to look like this:
+
+      Enum.map([:a, :b, :c], fn letter ->
+        letter
+        |> inspect()
+        |> String.upcase()
+      end)
+
+  The reason for this is performance, because the two separate calls
+  to `Enum.map/2` require two iterations whereas doing the functions
+  in the single `Enum.map/2` only requires one.
+  """
+
+  @explanation [check: @moduledoc]
+
+  use Credo.Check, base_priority: :high
+
+  @doc false
+  def run(source_file, params \\ []) do
+    issue_meta = IssueMeta.for(source_file, params)
+
+    message = "One `Enum.map/2` is more efficient than `Enum.map/2 |> Enum.map/2`"
+    trigger = "map_map"
+
+    Credo.Code.prewalk(
+      source_file,
+      &EnumHelpers.traverse(&1, &2, issue_meta, message, trigger, :map, :map, __MODULE__)
+    )
+  end
+end
diff --git a/lib/credo/check/refactor/reject_filter.ex b/lib/credo/check/refactor/reject_filter.ex
new file mode 100644
index 00000000..1d9c5ca8
--- /dev/null
+++ b/lib/credo/check/refactor/reject_filter.ex
@@ -0,0 +1,38 @@
+defmodule Credo.Check.Refactor.RejectFilter do
+  @moduledoc """
+  One `Enum.filter/2` is more efficient than `Enum.reject/2 |> Enum.filter/2`.
+
+  This should be refactored:
+
+      ["a", "b", "c"]
+      |> Enum.reject(&String.contains?(&1, "x"))
+      |> Enum.filter(&String.contains?(&1, "a"))
+
+  to look like this:
+
+      Enum.filter(["a", "b", "c"], fn letter ->
+        !String.contains?(letter, "x") && String.contains?(letter, "a")
+      end)
+
+  The reason for this is performance, because the two calls to
+  `Enum.reject/2` and `Enum.filter/2` require two iterations whereas
+  doing the functions in the single `Enum.filter/2` only requires one.
+  """
+
+  @explanation [check: @moduledoc]
+
+  use Credo.Check, base_priority: :high
+
+  @doc false
+  def run(source_file, params \\ []) do
+    issue_meta = IssueMeta.for(source_file, params)
+
+    message = "One `Enum.filter/2` is more efficient than `Enum.reject/2 |> Enum.filter/2`"
+    trigger = "reject_filter"
+
+    Credo.Code.prewalk(
+      source_file,
+      &EnumHelpers.traverse(&1, &2, issue_meta, message, trigger, :reject, :filter, __MODULE__)
+    )
+  end
+end
diff --git a/lib/credo/check/refactor/reject_reject.ex b/lib/credo/check/refactor/reject_reject.ex
new file mode 100644
index 00000000..2f245dbe
--- /dev/null
+++ b/lib/credo/check/refactor/reject_reject.ex
@@ -0,0 +1,38 @@
+defmodule Credo.Check.Refactor.RejectReject do
+  @moduledoc """
+  One `Enum.reject/2` is more efficient than `Enum.reject/2 |> Enum.reject/2`.
+
+  This should be refactored:
+
+      ["a", "b", "c"]
+      |> Enum.reject(&String.contains?(&1, "x"))
+      |> Enum.reject(&String.contains?(&1, "a"))
+
+  to look like this:
+
+      Enum.reject(["a", "b", "c"], fn letter ->
+        String.contains?(letter, "x") && String.contains?(letter, "a")
+      end)
+
+  The reason for this is performance, because the two separate calls
+  to `Enum.reject/2` require two iterations whereas doing the
+  functions in the single `Enum.reject/2` only requires one.
+  """
+
+  @explanation [check: @moduledoc]
+
+  use Credo.Check, base_priority: :high
+
+  @doc false
+  def run(source_file, params \\ []) do
+    issue_meta = IssueMeta.for(source_file, params)
+
+    message = "One `Enum.reject/2` is more efficient than `Enum.reject/2 |> Enum.reject/2`"
+    trigger = "reject_reject"
+
+    Credo.Code.prewalk(
+      source_file,
+      &EnumHelpers.traverse(&1, &2, issue_meta, message, trigger, :reject, :reject, __MODULE__)
+    )
+  end
+end


Test patch:
diff --git a/test/credo/check/refactor/filter_filter_test.exs b/test/credo/check/refactor/filter_filter_test.exs
new file mode 100644
index 00000000..d1292471
--- /dev/null
+++ b/test/credo/check/refactor/filter_filter_test.exs
@@ -0,0 +1,109 @@
+defmodule Credo.Check.Refactor.FilterFilterTest do
+  use Credo.TestHelper
+
+  @described_check Credo.Check.Refactor.FilterFilter
+
+  #
+  # cases NOT raising issues
+  #
+
+  test "it should NOT report expected code" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        Enum.filter(["a", "b", "c"], fn letter ->
+          !String.contains?(letter, "x") && String.contains?(letter, "a")
+        end)
+      end
+    end
+    """
+    |> to_source_file
+    |> refute_issues(@described_check)
+  end
+
+  #
+  # cases raising issues
+  #
+
+  test "it should report a violation" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        ["a", "b", "c"]
+        |> Enum.filter(&String.contains?(&1, "x"))
+        |> Enum.filter(&String.contains?(&1, "a"))
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 2" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5, p6) do
+        Enum.filter(Enum.filter([:a, :b, :c], &String.contains?(&1, "x")), &String.contains?(&1, "a"))
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 3" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        [:a, :b, :c]
+        |> Enum.sort()
+        |> Enum.filter(&String.contains?(&1, "x"))
+        |> Enum.filter(&String.contains?(&1, "a"))
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 4" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        [:a, :b, :c]
+        |> Enum.sort()
+        |> Enum.filter(&String.contains?(&1, "x"))
+        |> Enum.filter(&String.contains?(&1, "a"))
+        |> Enum.sort()
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 5" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        Enum.filter([:a, :b, :c], &String.contains?(&1, "x"))
+        |> Enum.filter(&String.contains?(&1, "a"))
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 6" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        Enum.filter([:a, :b, :c] |> Enum.filter(&String.contains?(&1, "x")), &String.contains?(&1, "a"))
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+end
diff --git a/test/credo/check/refactor/filter_reject_test.exs b/test/credo/check/refactor/filter_reject_test.exs
new file mode 100644
index 00000000..922d3e58
--- /dev/null
+++ b/test/credo/check/refactor/filter_reject_test.exs
@@ -0,0 +1,109 @@
+defmodule Credo.Check.Refactor.FilterRejectTest do
+  use Credo.TestHelper
+
+  @described_check Credo.Check.Refactor.FilterReject
+
+  #
+  # cases NOT raising issues
+  #
+
+  test "it should NOT report expected code" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        Enum.reject(["a", "b", "c"], fn letter ->
+          !String.contains?(letter, "x") && String.contains?(letter, "a")
+        end)
+      end
+    end
+    """
+    |> to_source_file
+    |> refute_issues(@described_check)
+  end
+
+  #
+  # cases raising issues
+  #
+
+  test "it should report a violation" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        ["a", "b", "c"]
+        |> Enum.filter(&String.contains?(&1, "x"))
+        |> Enum.reject(&String.contains?(&1, "a"))
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 2" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5, p6) do
+        Enum.reject(Enum.filter([:a, :b, :c], &String.contains?(&1, "x")), &String.contains?(&1, "a"))
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 3" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        [:a, :b, :c]
+        |> Enum.sort()
+        |> Enum.filter(&String.contains?(&1, "x"))
+        |> Enum.reject(&String.contains?(&1, "a"))
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 4" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        [:a, :b, :c]
+        |> Enum.sort()
+        |> Enum.filter(&String.contains?(&1, "x"))
+        |> Enum.reject(&String.contains?(&1, "a"))
+        |> Enum.sort()
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 5" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        Enum.filter([:a, :b, :c], &String.contains?(&1, "x"))
+        |> Enum.reject(&String.contains?(&1, "a"))
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 6" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        Enum.reject([:a, :b, :c] |> Enum.filter(&String.contains?(&1, "x")), &String.contains?(&1, "a"))
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+end
diff --git a/test/credo/check/refactor/map_map_test.exs b/test/credo/check/refactor/map_map_test.exs
new file mode 100644
index 00000000..80f9fcd1
--- /dev/null
+++ b/test/credo/check/refactor/map_map_test.exs
@@ -0,0 +1,111 @@
+defmodule Credo.Check.Refactor.MapMapTest do
+  use Credo.TestHelper
+
+  @described_check Credo.Check.Refactor.MapMap
+
+  #
+  # cases NOT raising issues
+  #
+
+  test "it should NOT report expected code" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        Enum.map([:a, :b, :c], fn letter ->
+          letter
+          |> inspect()
+          |> String.upcase()
+        end)
+      end
+    end
+    """
+    |> to_source_file
+    |> refute_issues(@described_check)
+  end
+
+  #
+  # cases raising issues
+  #
+
+  test "it should report a violation" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        [:a, :b, :c]
+        |> Enum.map(&inspect/1)
+        |> Enum.map(&String.upcase/1)
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 2" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5, p6) do
+        Enum.map(Enum.map([:a, :b, :c], &inspect/1), &String.upcase/1)
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 3" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        [:a, :b, :c]
+        |> Enum.sort()
+        |> Enum.map(&inspect/1)
+        |> Enum.map(&String.upcase/1)
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 4" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        [:a, :b, :c]
+        |> Enum.sort()
+        |> Enum.map(&inspect/1)
+        |> Enum.map(&String.upcase/1)
+        |> Enum.filter(&is_nil/1)
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 5" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        Enum.map([:a, :b, :c], &inspect/1)
+        |> Enum.map(&String.upcase/1)
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 6" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        Enum.map([:a, :b, :c] |> Enum.map(&inspect/1), &String.downcase/1)
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+end
diff --git a/test/credo/check/refactor/reject_filter_test.exs b/test/credo/check/refactor/reject_filter_test.exs
new file mode 100644
index 00000000..bf92c557
--- /dev/null
+++ b/test/credo/check/refactor/reject_filter_test.exs
@@ -0,0 +1,109 @@
+defmodule Credo.Check.Refactor.RejectFilterTest do
+  use Credo.TestHelper
+
+  @described_check Credo.Check.Refactor.RejectFilter
+
+  #
+  # cases NOT raising issues
+  #
+
+  test "it should NOT report expected code" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        Enum.filter(["a", "b", "c"], fn letter ->
+          !String.contains?(letter, "x") && String.contains?(letter, "a")
+        end)
+      end
+    end
+    """
+    |> to_source_file
+    |> refute_issues(@described_check)
+  end
+
+  #
+  # cases raising issues
+  #
+
+  test "it should report a violation" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        ["a", "b", "c"]
+        |> Enum.reject(&String.contains?(&1, "x"))
+        |> Enum.filter(&String.contains?(&1, "a"))
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 2" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5, p6) do
+        Enum.filter(Enum.reject([:a, :b, :c], &String.contains?(&1, "x")), &String.contains?(&1, "a"))
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 3" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        [:a, :b, :c]
+        |> Enum.sort()
+        |> Enum.reject(&String.contains?(&1, "x"))
+        |> Enum.filter(&String.contains?(&1, "a"))
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 4" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        [:a, :b, :c]
+        |> Enum.sort()
+        |> Enum.reject(&String.contains?(&1, "x"))
+        |> Enum.filter(&String.contains?(&1, "a"))
+        |> Enum.sort()
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 5" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        Enum.reject([:a, :b, :c], &String.contains?(&1, "x"))
+        |> Enum.filter(&String.contains?(&1, "a"))
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 6" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        Enum.filter([:a, :b, :c] |> Enum.reject(&String.contains?(&1, "x")), &String.contains?(&1, "a"))
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+end
diff --git a/test/credo/check/refactor/reject_reject_test.exs b/test/credo/check/refactor/reject_reject_test.exs
new file mode 100644
index 00000000..f305c106
--- /dev/null
+++ b/test/credo/check/refactor/reject_reject_test.exs
@@ -0,0 +1,109 @@
+defmodule Credo.Check.Refactor.RejectRejectTest do
+  use Credo.TestHelper
+
+  @described_check Credo.Check.Refactor.RejectReject
+
+  #
+  # cases NOT raising issues
+  #
+
+  test "it should NOT report expected code" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        Enum.reject(["a", "b", "c"], fn letter ->
+          !String.contains?(letter, "x") && String.contains?(letter, "a")
+        end)
+      end
+    end
+    """
+    |> to_source_file
+    |> refute_issues(@described_check)
+  end
+
+  #
+  # cases raising issues
+  #
+
+  test "it should report a violation" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        ["a", "b", "c"]
+        |> Enum.reject(&String.contains?(&1, "x"))
+        |> Enum.reject(&String.contains?(&1, "a"))
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 2" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5, p6) do
+        Enum.reject(Enum.reject([:a, :b, :c], &String.contains?(&1, "x")), &String.contains?(&1, "a"))
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 3" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        [:a, :b, :c]
+        |> Enum.sort()
+        |> Enum.reject(&String.contains?(&1, "x"))
+        |> Enum.reject(&String.contains?(&1, "a"))
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 4" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        [:a, :b, :c]
+        |> Enum.sort()
+        |> Enum.reject(&String.contains?(&1, "x"))
+        |> Enum.reject(&String.contains?(&1, "a"))
+        |> Enum.sort()
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 5" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        Enum.reject([:a, :b, :c], &String.contains?(&1, "x"))
+        |> Enum.reject(&String.contains?(&1, "a"))
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+
+  test "it should report a violation 6" do
+    """
+    defmodule Credo.Sample.Module do
+      def some_function(p1, p2, p3, p4, p5) do
+        Enum.reject([:a, :b, :c] |> Enum.reject(&String.contains?(&1, "x")), &String.contains?(&1, "a"))
+      end
+    end
+    """
+    |> to_source_file
+    |> assert_issue(@described_check)
+  end
+end