From 324c52eac2b444e3622d927e7f8cec9a5b2bd075 Mon Sep 17 00:00:00 2001 From: Angelika Tyborska Date: Sat, 24 Apr 2021 11:07:54 +0200 Subject: [PATCH] Add Credo (#56) --- .credo.exs | 199 ++++++++++++++++++ .github/workflows/elixir_test.yml | 2 + .tool-versions | 2 + lib/elixir_analyzer.ex | 12 +- lib/elixir_analyzer/cli.ex | 4 + lib/elixir_analyzer/constants.ex | 10 + lib/elixir_analyzer/exercise_test.ex | 27 ++- lib/elixir_analyzer/exercise_test/feature.ex | 11 +- .../exercise_test/feature/compiler.ex | 42 ++-- lib/elixir_analyzer/log_formatter.ex | 4 + lib/elixir_analyzer/submission.ex | 14 +- lib/elixir_analyzer/summary.ex | 8 +- .../test_suite/pacman_rules.ex | 0 .../test_suite/take_a_number.ex | 0 .../test_suite/two_fer.ex | 0 mix.exs | 5 +- mix.lock | 3 + .../exercise_test/assert_call_test.exs | 12 +- test/support/exercise_test_case.ex | 62 +++--- 19 files changed, 327 insertions(+), 90 deletions(-) create mode 100644 .credo.exs create mode 100644 .tool-versions rename lib/{ => elixir_analyzer}/test_suite/pacman_rules.ex (100%) rename lib/{ => elixir_analyzer}/test_suite/take_a_number.ex (100%) rename lib/{ => elixir_analyzer}/test_suite/two_fer.ex (100%) diff --git a/.credo.exs b/.credo.exs new file mode 100644 index 00000000..243b4462 --- /dev/null +++ b/.credo.exs @@ -0,0 +1,199 @@ +# This file contains the configuration for Credo and you are probably reading +# this after creating it with `mix credo.gen.config`. +# +# If you find anything wrong or unclear in this file, please report an +# issue on GitHub: https://github.com/rrrene/credo/issues +# +%{ + # + # You can have as many configs as you like in the `configs:` field. + configs: [ + %{ + # + # Run any config using `mix credo -C `. If no config name is given + # "default" is used. + # + name: "default", + # + # These are the files included in the analysis: + files: %{ + # + # You can give explicit globs or simply directories. + # In the latter case `**/*.{ex,exs}` will be used. + # + included: [ + "lib/", + "src/", + "test/", + "web/", + "apps/*/lib/", + "apps/*/src/", + "apps/*/test/", + "apps/*/web/" + ], + excluded: [ + ~r"/_build/", + ~r"/deps/", + ~r"/node_modules/", + # exclude exercise analysis because of snippets in `feature do form do...` + # they can be unusual, e.g. `_ignore || _ignore` + "lib/elixir_analyzer/test_suite/", + # exclude exercise analysis test because of example solutions used in tests + # those mimic student solutions and might be "bad code" on purpose + "test/elixir_analyzer/test_suite/" + ] + }, + # + # Load and configure plugins here: + # + plugins: [], + # + # If you create your own checks, you must specify the source files for + # them here, so they can be loaded by Credo before running the analysis. + # + requires: [], + # + # If you want to enforce a style guide and need a more traditional linting + # experience, you can change `strict` to `true` below: + # + strict: false, + # + # To modify the timeout for parsing files, change this value: + # + parse_timeout: 5000, + # + # If you want to use uncolored output by default, you can change `color` + # to `false` below: + # + color: true, + # + # You can customize the parameters of any check by adding a second element + # to the tuple. + # + # To disable a check put `false` as second element: + # + # {Credo.Check.Design.DuplicatedCode, false} + # + checks: [ + # + ## Consistency Checks + # + {Credo.Check.Consistency.ExceptionNames, []}, + {Credo.Check.Consistency.LineEndings, []}, + {Credo.Check.Consistency.ParameterPatternMatching, []}, + {Credo.Check.Consistency.SpaceAroundOperators, []}, + {Credo.Check.Consistency.SpaceInParentheses, []}, + {Credo.Check.Consistency.TabsOrSpaces, []}, + + # + ## Design Checks + # + # You can customize the priority of any check + # Priority values are: `low, normal, high, higher` + # + {Credo.Check.Design.AliasUsage, + [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, + # You can also customize the exit_status of each check. + # If you don't want TODO comments to cause `mix credo` to fail, just + # set this value to 0 (zero). + # + {Credo.Check.Design.TagTODO, [exit_status: 2]}, + {Credo.Check.Design.TagFIXME, []}, + + # + ## Readability Checks + # + {Credo.Check.Readability.AliasOrder, []}, + {Credo.Check.Readability.FunctionNames, []}, + {Credo.Check.Readability.LargeNumbers, []}, + {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, + {Credo.Check.Readability.ModuleAttributeNames, []}, + {Credo.Check.Readability.ModuleDoc, []}, + {Credo.Check.Readability.ModuleNames, []}, + {Credo.Check.Readability.ParenthesesInCondition, []}, + {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, + {Credo.Check.Readability.PredicateFunctionNames, []}, + {Credo.Check.Readability.PreferImplicitTry, []}, + {Credo.Check.Readability.RedundantBlankLines, []}, + {Credo.Check.Readability.Semicolons, []}, + {Credo.Check.Readability.SpaceAfterCommas, []}, + {Credo.Check.Readability.StringSigils, []}, + {Credo.Check.Readability.TrailingBlankLine, []}, + {Credo.Check.Readability.TrailingWhiteSpace, []}, + {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, + {Credo.Check.Readability.VariableNames, []}, + + # + ## Refactoring Opportunities + # + {Credo.Check.Refactor.CondStatements, []}, + {Credo.Check.Refactor.CyclomaticComplexity, []}, + {Credo.Check.Refactor.FunctionArity, []}, + {Credo.Check.Refactor.LongQuoteBlocks, []}, + # {Credo.Check.Refactor.MapInto, []}, + {Credo.Check.Refactor.MatchInCondition, []}, + {Credo.Check.Refactor.NegatedConditionsInUnless, []}, + {Credo.Check.Refactor.NegatedConditionsWithElse, []}, + {Credo.Check.Refactor.Nesting, []}, + {Credo.Check.Refactor.UnlessWithElse, []}, + {Credo.Check.Refactor.WithClauses, []}, + + # + ## Warnings + # + {Credo.Check.Warning.ApplicationConfigInModuleAttribute, []}, + {Credo.Check.Warning.BoolOperationOnSameValues, []}, + {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, + {Credo.Check.Warning.IExPry, []}, + {Credo.Check.Warning.IoInspect, []}, + # {Credo.Check.Warning.LazyLogging, []}, + {Credo.Check.Warning.MixEnv, false}, + {Credo.Check.Warning.OperationOnSameValues, []}, + {Credo.Check.Warning.OperationWithConstantResult, []}, + {Credo.Check.Warning.RaiseInsideRescue, []}, + {Credo.Check.Warning.UnusedEnumOperation, []}, + {Credo.Check.Warning.UnusedFileOperation, []}, + {Credo.Check.Warning.UnusedKeywordOperation, []}, + {Credo.Check.Warning.UnusedListOperation, []}, + {Credo.Check.Warning.UnusedPathOperation, []}, + {Credo.Check.Warning.UnusedRegexOperation, []}, + {Credo.Check.Warning.UnusedStringOperation, []}, + {Credo.Check.Warning.UnusedTupleOperation, []}, + {Credo.Check.Warning.UnsafeExec, []}, + + # + # Checks scheduled for next check update (opt-in for now, just replace `false` with `[]`) + + # + # Controversial and experimental checks (opt-in, just replace `false` with `[]`) + # + {Credo.Check.Consistency.MultiAliasImportRequireUse, false}, + {Credo.Check.Consistency.UnusedVariableNames, false}, + {Credo.Check.Design.DuplicatedCode, false}, + {Credo.Check.Readability.AliasAs, false}, + {Credo.Check.Readability.BlockPipe, false}, + {Credo.Check.Readability.ImplTrue, false}, + {Credo.Check.Readability.MultiAlias, false}, + {Credo.Check.Readability.SeparateAliasRequire, false}, + {Credo.Check.Readability.SinglePipe, false}, + {Credo.Check.Readability.Specs, false}, + {Credo.Check.Readability.StrictModuleLayout, false}, + {Credo.Check.Readability.WithCustomTaggedTuple, false}, + {Credo.Check.Refactor.ABCSize, false}, + {Credo.Check.Refactor.AppendSingleItem, false}, + {Credo.Check.Refactor.DoubleBooleanNegation, false}, + {Credo.Check.Refactor.ModuleDependencies, false}, + {Credo.Check.Refactor.NegatedIsNil, false}, + {Credo.Check.Refactor.PipeChainStart, false}, + {Credo.Check.Refactor.VariableRebinding, false}, + {Credo.Check.Warning.LeakyEnvironment, false}, + {Credo.Check.Warning.MapGetUnsafePass, false}, + {Credo.Check.Warning.UnsafeToAtom, false} + + # + # Custom checks can be created using `mix credo.gen.check`. + # + ] + } + ] +} diff --git a/.github/workflows/elixir_test.yml b/.github/workflows/elixir_test.yml index 4ab2bf8f..a1ea3a6a 100644 --- a/.github/workflows/elixir_test.yml +++ b/.github/workflows/elixir_test.yml @@ -18,3 +18,5 @@ jobs: mix deps.get - name: Run Tests run: mix test --exclude external + - name: Run Credo + run: mix credo diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 00000000..4e9454a7 --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +elixir 1.11.3-otp-23 +erlang 23.2.6 diff --git a/lib/elixir_analyzer.ex b/lib/elixir_analyzer.ex index d0fe479e..dbee020a 100644 --- a/lib/elixir_analyzer.ex +++ b/lib/elixir_analyzer.ex @@ -12,7 +12,7 @@ defmodule ElixirAnalyzer do import ElixirAnalyzer.Summary, only: [summary: 2] # defaults - @exercise_config Application.get_env(:elixir_analyzer, :exercise_config) + @exercise_config Application.compile_env(:elixir_analyzer, :exercise_config) @output_file "analysis.json" @meta_config ".meta/config.json" @@ -179,12 +179,12 @@ defmodule ElixirAnalyzer do # - check if the file exists # - read in the code # - compile - defp check(submission = %Submission{halted: true}, _params) do + defp check(%Submission{halted: true} = submission, _params) do Logger.warning("Check not performed, halted previously") submission end - defp check(submission = %Submission{}, _params) do + defp check(%Submission{} = submission, _params) do with path_to_code <- Path.join(submission.code_path, submission.code_file), :ok <- Logger.info("Attempting to read code file", code_file_path: path_to_code), {:code_read, {:ok, code_str}} <- {:code_read, File.read(path_to_code)}, @@ -213,12 +213,12 @@ defmodule ElixirAnalyzer do # Analyze # - Start the static analysis - defp analyze(submission = %Submission{halted: true}, _params) do + defp analyze(%Submission{halted: true} = submission, _params) do Logger.info("Analysis not performed, halted previously") submission end - defp analyze(submission = %Submission{}, _params) do + defp analyze(%Submission{} = submission, _params) do Logger.info("Analyzing code started") submission = @@ -230,7 +230,7 @@ defmodule ElixirAnalyzer do submission end - defp write_results(submission = %Submission{}, params) do + defp write_results(%Submission{} = submission, params) do if params.write_results do output_file_path = Path.join(params.output_path, params.output_file) Logger.info("Writing final results.json to file", path: output_file_path) diff --git a/lib/elixir_analyzer/cli.ex b/lib/elixir_analyzer/cli.ex index 5c877b1f..44068de4 100644 --- a/lib/elixir_analyzer/cli.ex +++ b/lib/elixir_analyzer/cli.ex @@ -1,4 +1,8 @@ defmodule ElixirAnalyzer.CLI do + @moduledoc """ + A CLI for running analysis on a single solution. + """ + @usage """ Usage: diff --git a/lib/elixir_analyzer/constants.ex b/lib/elixir_analyzer/constants.ex index e4aada28..e869acb4 100644 --- a/lib/elixir_analyzer/constants.ex +++ b/lib/elixir_analyzer/constants.ex @@ -1,4 +1,14 @@ defmodule ElixirAnalyzer.Constants do + @moduledoc """ + A list of Elixir analyzer comments, in the format: + ``` + elixir.[directory].[filename] + ``` + + `[directory]` must correspond to a directory in https://github.com/exercism/website-copy/tree/main/analyzer-comments/elixir + and `[filename].md` must be a file in that directory. + """ + @constants [ # Status Comments # status_approve: "elixir.status.approve", diff --git a/lib/elixir_analyzer/exercise_test.ex b/lib/elixir_analyzer/exercise_test.ex index ee6738fb..ddeb99fb 100644 --- a/lib/elixir_analyzer/exercise_test.ex +++ b/lib/elixir_analyzer/exercise_test.ex @@ -23,6 +23,7 @@ defmodule ElixirAnalyzer.ExerciseTest do # defmacro __before_compile__(env) do + # credo:disable-for-previous-line Credo.Check.Refactor.CyclomaticComplexity feature_test_data = Macro.escape(Module.get_attribute(env.module, :feature_tests)) assert_call_data = Module.get_attribute(env.module, :assert_call_tests) @@ -37,7 +38,7 @@ defmodule ElixirAnalyzer.ExerciseTest do quote do @spec analyze(Submission.t(), String.t()) :: Submission.t() - def analyze(submission = %Submission{}, code_as_string) do + def analyze(%Submission{} = submission, code_as_string) do case Code.string_to_quoted(code_as_string) do {:ok, code_ast} -> feature_results = unquote(feature_tests) |> filter_suppressed_results() @@ -56,21 +57,25 @@ defmodule ElixirAnalyzer.ExerciseTest do feature_results |> Enum.reject(fn {_test_result, %{suppress_if: condition}} when condition !== false -> - [suppress_on_test_name, suppress_on_result] = condition - - Enum.any?(feature_results, fn {result, test} -> - case {result, test.name} do - {^suppress_on_result, ^suppress_on_test_name} -> true - _ -> false - end - end) + any_result_matches_suppress_condition?(feature_results, condition) _result -> false end) end - defp append_test_comments(submission = %Submission{}, results) do + defp any_result_matches_suppress_condition?(feature_results, condition) do + [suppress_on_test_name, suppress_on_result] = condition + + Enum.any?(feature_results, fn {result, test} -> + case {result, test.name} do + {^suppress_on_result, ^suppress_on_test_name} -> true + _ -> false + end + end) + end + + defp append_test_comments(%Submission{} = submission, results) do Enum.reduce(results, submission, fn {:skip, _description}, submission -> submission @@ -94,7 +99,7 @@ defmodule ElixirAnalyzer.ExerciseTest do end) end - defp append_analysis_failure(submission = %Submission{}, {location, error, token}) do + defp append_analysis_failure(%Submission{} = submission, {location, error, token}) do line = case location do location when is_integer(location) -> location diff --git a/lib/elixir_analyzer/exercise_test/feature.ex b/lib/elixir_analyzer/exercise_test/feature.ex index 9f681ce3..ea2b77ad 100644 --- a/lib/elixir_analyzer/exercise_test/feature.ex +++ b/lib/elixir_analyzer/exercise_test/feature.ex @@ -1,4 +1,9 @@ defmodule ElixirAnalyzer.ExerciseTest.Feature do + @moduledoc """ + Defines a `feature` macro that allows looking for specific snippets + whose AST matches part of the AST of the solution. + """ + @doc false defmacro __using__(_opts) do quote do @@ -54,13 +59,13 @@ defmodule ElixirAnalyzer.ExerciseTest.Feature do defp gather_feature_data({:form, _, [[do: form]]} = node, acc) do ast = - unless acc.meta.keep_meta do + if acc.meta.keep_meta do + form + else Macro.prewalk(form, fn {name, _, param} -> {name, :_ignore, param} node -> node end) - else - form end {ast, block_params} = diff --git a/lib/elixir_analyzer/exercise_test/feature/compiler.ex b/lib/elixir_analyzer/exercise_test/feature/compiler.ex index e0c23aba..d3fb5c02 100644 --- a/lib/elixir_analyzer/exercise_test/feature/compiler.ex +++ b/lib/elixir_analyzer/exercise_test/feature/compiler.ex @@ -73,23 +73,21 @@ defmodule ElixirAnalyzer.ExerciseTest.Feature.Compiler do {:__block__, _, params} = node, false, depth -> finding_depth = unquote(find_at_depth) in [nil, depth] - cond do - finding_depth and is_list(params) -> - found = - params - |> Enum.chunk_every(unquote(block_params), 1, :discard) - |> Enum.reduce(false, fn - chunk, false -> - match?(unquote(find_ast), chunk) - - _chunk, true -> - true - end) - - {node, found} - - true -> - {node, false} + if finding_depth and is_list(params) do + found = + params + |> Enum.chunk_every(unquote(block_params), 1, :discard) + |> Enum.reduce(false, fn + chunk, false -> + match?(unquote(find_ast), chunk) + + _chunk, true -> + true + end) + + {node, found} + else + {node, false} end # If not a block, then we know it can't match, so pass @@ -106,12 +104,10 @@ defmodule ElixirAnalyzer.ExerciseTest.Feature.Compiler do node, false, depth -> finding_depth = unquote(find_at_depth) in [nil, depth] - cond do - finding_depth -> - {node, match?(unquote(find_ast), node)} - - true -> - {node, false} + if finding_depth do + {node, match?(unquote(find_ast), node)} + else + {node, false} end node, true, _depth -> diff --git a/lib/elixir_analyzer/log_formatter.ex b/lib/elixir_analyzer/log_formatter.ex index 6bad1650..6743abf9 100644 --- a/lib/elixir_analyzer/log_formatter.ex +++ b/lib/elixir_analyzer/log_formatter.ex @@ -1,4 +1,8 @@ defmodule ElixirAnalyzer.LogFormatter do + @moduledoc """ + Analyzer's custom formatter for the Logger. + """ + def format(level, message, timestamp, metadata) do "# #{fmt_timestamp(timestamp)} #{inspect(metadata)} [#{level}] #{message}\n" rescue diff --git a/lib/elixir_analyzer/submission.ex b/lib/elixir_analyzer/submission.ex index 31d35232..fdf6270a 100644 --- a/lib/elixir_analyzer/submission.ex +++ b/lib/elixir_analyzer/submission.ex @@ -48,26 +48,26 @@ defmodule ElixirAnalyzer.Submission do analysis_module: atom() } - def to_json(submission = %__MODULE__{}) do + def to_json(%__MODULE__{} = submission) do Jason.encode!(%{summary: get_summary(submission), comments: submission.comments}) end @doc false - def halt(submission = %__MODULE__{}) do + def halt(%__MODULE__{} = submission) do %{submission | halted: true} end - def set_halt_reason(submission = %__MODULE__{}, reason) when is_binary(reason) do + def set_halt_reason(%__MODULE__{} = submission, reason) when is_binary(reason) do %{submission | halt_reason: reason} end @doc false - def set_analyzed(submission = %__MODULE__{}, value) when is_boolean(value) do + def set_analyzed(%__MODULE__{} = submission, value) when is_boolean(value) do %{submission | analyzed: value} end @doc false - def append_comment(submission = %__MODULE__{}, meta) when is_map(meta) do + def append_comment(%__MODULE__{} = submission, meta) when is_map(meta) do comment = Enum.filter(meta, fn {key, _} when key in [:comment, :type, :params] -> true @@ -78,8 +78,8 @@ defmodule ElixirAnalyzer.Submission do %{submission | comments: submission.comments ++ [comment]} end - defp get_summary(submission = %__MODULE__{halted: true, comments: comments}) - when length(comments) == 0 do + defp get_summary(%__MODULE__{halted: true, comments: comments} = submission) + when comments == [] do case submission.halt_reason do nil -> "Analysis was halted." _ -> "Analysis was halted. #{submission.halt_reason}" diff --git a/lib/elixir_analyzer/summary.ex b/lib/elixir_analyzer/summary.ex index d54d4651..d3a7e3e0 100644 --- a/lib/elixir_analyzer/summary.ex +++ b/lib/elixir_analyzer/summary.ex @@ -9,19 +9,19 @@ defmodule ElixirAnalyzer.Summary do From the Submission, return a string representation of the Analysis summary """ @spec summary(Submission.t(), map()) :: String.t() - def summary(s = %Submission{}, params) do + def summary(%Submission{} = submission, params) do """ ElixirAnalyzer Report --------------------- Exercise: #{params.exercise} - Status: #{result_to_string(s)} + Status: #{result_to_string(submission)} Output written to ... #{Path.join(params.output_path, params.output_file)} """ end - defp result_to_string(s = %Submission{}) do - case {s.halted, s.analyzed} do + defp result_to_string(%Submission{} = submission) do + case {submission.halted, submission.analyzed} do {true, _} -> "Halted" {_, false} -> "Analysis Incomplete" {_, true} -> "Analysis Complete" diff --git a/lib/test_suite/pacman_rules.ex b/lib/elixir_analyzer/test_suite/pacman_rules.ex similarity index 100% rename from lib/test_suite/pacman_rules.ex rename to lib/elixir_analyzer/test_suite/pacman_rules.ex diff --git a/lib/test_suite/take_a_number.ex b/lib/elixir_analyzer/test_suite/take_a_number.ex similarity index 100% rename from lib/test_suite/take_a_number.ex rename to lib/elixir_analyzer/test_suite/take_a_number.ex diff --git a/lib/test_suite/two_fer.ex b/lib/elixir_analyzer/test_suite/two_fer.ex similarity index 100% rename from lib/test_suite/two_fer.ex rename to lib/elixir_analyzer/test_suite/two_fer.ex diff --git a/mix.exs b/mix.exs index 8ed67d37..c9c04741 100644 --- a/mix.exs +++ b/mix.exs @@ -22,7 +22,10 @@ defmodule ElixirAnalyzer.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do - [{:jason, "~> 1.2"}] + [ + {:jason, "~> 1.2"}, + {:credo, "~> 1.5", only: [:dev, :test], runtime: false} + ] end defp elixirc_paths(:test), do: ["lib", "test/support"] diff --git a/mix.lock b/mix.lock index 98b8604b..3257081e 100644 --- a/mix.lock +++ b/mix.lock @@ -1,3 +1,6 @@ %{ + "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, + "credo": {:hex, :credo, "1.5.5", "e8f422026f553bc3bebb81c8e8bf1932f498ca03339856c7fec63d3faac8424b", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dd8623ab7091956a855dc9f3062486add9c52d310dfd62748779c4315d8247de"}, + "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, } diff --git a/test/elixir_analyzer/exercise_test/assert_call_test.exs b/test/elixir_analyzer/exercise_test/assert_call_test.exs index 6bba0a27..682263c3 100644 --- a/test/elixir_analyzer/exercise_test/assert_call_test.exs +++ b/test/elixir_analyzer/exercise_test/assert_call_test.exs @@ -6,7 +6,7 @@ defmodule ElixirAnalyzer.ExerciseTest.AssertCallTest do comments: [] do defmodule AssertCallVerification do def function() do - List.first([1, 2, 3]) + x = List.first([1, 2, 3]) result = helper() IO.puts(result) @@ -30,7 +30,7 @@ defmodule ElixirAnalyzer.ExerciseTest.AssertCallTest do ] do defmodule AssertCallVerification do def function() do - List.last([1, 2, 3]) + x = List.last([1, 2, 3]) private_helper() |> IO.puts() end @@ -51,7 +51,7 @@ defmodule ElixirAnalyzer.ExerciseTest.AssertCallTest do ] do defmodule AssertCallVerification do def function() do - List.first([1, 2, 3]) + x = List.first([1, 2, 3]) other() IO.puts("1") end @@ -78,7 +78,7 @@ defmodule ElixirAnalyzer.ExerciseTest.AssertCallTest do ] do defmodule AssertCallVerification do def function() do - List.flatten([1, 2, 3]) + l = List.flatten([1, 2, 3]) result = helper() private_helper() end @@ -99,7 +99,7 @@ defmodule ElixirAnalyzer.ExerciseTest.AssertCallTest do ] do defmodule AssertCallVerification do def function() do - List.first([1, 2, 3]) + l = List.first([1, 2, 3]) result = helper() private_helper() |> other() end @@ -131,7 +131,7 @@ defmodule ElixirAnalyzer.ExerciseTest.AssertCallTest do end def helper do - List.first([1, 2, 3]) + l = List.first([1, 2, 3]) :helped end diff --git a/test/support/exercise_test_case.ex b/test/support/exercise_test_case.ex index b87b8729..c079cffb 100644 --- a/test/support/exercise_test_case.ex +++ b/test/support/exercise_test_case.ex @@ -100,38 +100,42 @@ defmodule ElixirAnalyzer.ExerciseTestCase do |> Enum.map(fn comment_details -> comment_details.comment end) Enum.map(Keyword.keys(unquote(assertions)), fn key -> - case key do - :comments -> - expected_comments = unquote(assertions[:comments]) - - if expected_comments do - assert Enum.sort(comments) == Enum.sort(expected_comments) - end - - :comments_include -> - comments_include = unquote(assertions[:comments_include]) - - if comments_include do - Enum.each(comments_include, fn comment -> - assert comment in comments - end) - end - - :comments_exclude -> - comments_exclude = unquote(assertions[:comments_exclude]) - - if comments_exclude do - Enum.each(comments_exclude, fn comment -> - refute comment in comments - end) - end - - _ -> - :noop - end + assert_comments(comments, key, unquote(assertions)) end) end end end) end + + def assert_comments(comments, :comments, assertions) do + expected_comments = assertions[:comments] + + if expected_comments do + assert Enum.sort(comments) == Enum.sort(expected_comments) + end + end + + def assert_comments(comments, :comments_include, assertions) do + comments_include = assertions[:comments_include] + + if comments_include do + Enum.each(comments_include, fn comment -> + assert comment in comments + end) + end + end + + def assert_comments(comments, :comments_exclude, assertions) do + comments_exclude = assertions[:comments_exclude] + + if comments_exclude do + Enum.each(comments_exclude, fn comment -> + refute comment in comments + end) + end + end + + def assert_comments(_, _, _) do + :noop + end end