From 22a6664638b7c6b80f5828239ef2ab9257260017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobiasz=20Ma=C5=82ecki?= Date: Sun, 28 Sep 2025 15:28:57 +0200 Subject: [PATCH] Add credo --- .credo.exs | 72 +++++++++++++++++++++++++++++++++ .github/workflows/elixir-ci.yml | 20 +++++++++ lib/cache_decorator.ex | 33 +++++++++------ mix.exs | 1 + mix.lock | 4 ++ 5 files changed, 117 insertions(+), 13 deletions(-) create mode 100644 .credo.exs diff --git a/.credo.exs b/.credo.exs new file mode 100644 index 0000000..08aa5e7 --- /dev/null +++ b/.credo.exs @@ -0,0 +1,72 @@ +%{ + configs: [ + %{ + name: "default", + files: %{ + included: ["lib/", "src/", "web/", "apps/", "test/"], + excluded: [~r"/_build/", ~r"/deps/"] + }, + requires: [], + strict: true, + color: true, + checks: [ + {Credo.Check.Consistency.ExceptionNames}, + {Credo.Check.Consistency.LineEndings}, + {Credo.Check.Consistency.MultiAliasImportRequireUse}, + {Credo.Check.Consistency.ParameterPatternMatching}, + {Credo.Check.Consistency.SpaceAroundOperators}, + {Credo.Check.Consistency.SpaceInParentheses}, + {Credo.Check.Consistency.TabsOrSpaces}, + {Credo.Check.Design.AliasUsage, false}, + {Credo.Check.Design.DuplicatedCode, excluded_macros: []}, + {Credo.Check.Design.TagFIXME}, + {Credo.Check.Design.TagTODO, false}, + {Credo.Check.Readability.FunctionNames}, + {Credo.Check.Readability.LargeNumbers}, + {Credo.Check.Readability.MaxLineLength, max_length: 100}, + {Credo.Check.Readability.ModuleAttributeNames}, + {Credo.Check.Readability.ModuleDoc}, + {Credo.Check.Readability.ModuleNames}, + {Credo.Check.Readability.ParenthesesOnZeroArityDefs}, + {Credo.Check.Readability.ParenthesesInCondition}, + {Credo.Check.Readability.PredicateFunctionNames}, + {Credo.Check.Readability.PreferImplicitTry}, + {Credo.Check.Readability.RedundantBlankLines}, + {Credo.Check.Readability.Semicolons}, + {Credo.Check.Readability.SinglePipe, false}, + {Credo.Check.Readability.SpaceAfterCommas}, + {Credo.Check.Readability.Specs, false}, + {Credo.Check.Readability.StringSigils}, + {Credo.Check.Readability.TrailingBlankLine}, + {Credo.Check.Readability.TrailingWhiteSpace}, + {Credo.Check.Readability.VariableNames}, + {Credo.Check.Refactor.DoubleBooleanNegation}, + {Credo.Check.Refactor.CondStatements}, + {Credo.Check.Refactor.CyclomaticComplexity}, + {Credo.Check.Refactor.FunctionArity}, + {Credo.Check.Refactor.LongQuoteBlocks}, + {Credo.Check.Refactor.MatchInCondition}, + {Credo.Check.Refactor.NegatedConditionsInUnless}, + {Credo.Check.Refactor.NegatedConditionsWithElse}, + {Credo.Check.Refactor.Nesting}, + {Credo.Check.Refactor.PipeChainStart, false}, + {Credo.Check.Refactor.UnlessWithElse}, + {Credo.Check.Warning.BoolOperationOnSameValues}, + {Credo.Check.Warning.IExPry}, + {Credo.Check.Warning.IoInspect}, + {Credo.Check.Warning.LazyLogging, 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} + ] + } + ] +} diff --git a/.github/workflows/elixir-ci.yml b/.github/workflows/elixir-ci.yml index 2881da7..586e073 100644 --- a/.github/workflows/elixir-ci.yml +++ b/.github/workflows/elixir-ci.yml @@ -45,12 +45,31 @@ jobs: - name: Generate docs run: mix docs --warnings-as-errors + credo-check: + name: Check Mix Credo Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup Elixir + uses: erlef/setup-elixir@v1 + with: + elixir-version: 1.18 + otp-version: 28 + + - name: Fetch Dependencies + run: mix deps.get + + - name: Run Credo Linter + run: mix credo --strict --format=flycheck + test: name: Run Tests runs-on: ubuntu-latest needs: - format-check - docs-check + - credo-check strategy: matrix: elixir: [1.18, 1.19] @@ -79,6 +98,7 @@ jobs: needs: - format-check - docs-check + - credo-check strategy: matrix: elixir: [1.15, 1.16, 1.17, 1.18] diff --git a/lib/cache_decorator.ex b/lib/cache_decorator.ex index 96f5b62..4ee3e1c 100644 --- a/lib/cache_decorator.ex +++ b/lib/cache_decorator.ex @@ -212,7 +212,7 @@ defmodule CacheDecorator do Module.make_overridable(env.module, [{name, length(args_ast)}]) key_template = Keyword.fetch!(opts, :key) - key_ast = compile_key_ast!(key_template, args_ast, env.module, name, :cache) + key_ast = compile_key_ast!({key_template, args_ast, env.module, name, :cache}) quote do def unquote(name)(unquote_splicing(args_ast)) do @@ -245,7 +245,7 @@ defmodule CacheDecorator do Module.make_overridable(env.module, [{name, length(args)}]) raw_key = Keyword.fetch!(opts, :key) - key_ast = compile_key_ast!(raw_key, args, env.module, name, :invalidate) + key_ast = compile_key_ast!({raw_key, args, env.module, name, :invalidate}) invalidate_ast = quote do @@ -298,8 +298,9 @@ defmodule CacheDecorator do List.flatten(patterns_ast ++ [unmatched_pattern_ast]) end - defp compile_key_ast!(template, args_ast, env_module, fun_name, decorator_type) - when is_binary(template) do + defp compile_key_ast!({template, _, _, _, _} = args) when is_binary(template) do + {template, args_ast, env_module, fun_name, decorator_type} = args + # Collect *all* bound var names from the function head (deep walk) {^args_ast, arg_ast_by_name} = Macro.prewalk(args_ast, %{}, fn @@ -319,15 +320,7 @@ defmodule CacheDecorator do # If seg is exactly "{name}", treat it as a variable; otherwise literal. case Regex.run(~r/^\{([A-Za-z_][A-Za-z0-9_]*)\}$/u, seg) do [_, var] -> - case Map.fetch(arg_ast_by_name, var) do - {:ok, v_ast} -> - quote(do: to_string(unquote(v_ast))) - - :error -> - raise ArgumentError, - "#{inspect(__MODULE__)}: unknown variable {#{var}} in :key for " <> - "@#{decorator_type} #{inspect(env_module)}.#{fun_name}/#{length(args_ast)}" - end + handle_var(var, arg_ast_by_name, args) nil -> quote(do: unquote(seg)) @@ -349,6 +342,20 @@ defmodule CacheDecorator do end end + defp handle_var(var, arg_ast_by_name, args) do + {_template, args_ast, env_module, fun_name, decorator_type} = args + + case Map.fetch(arg_ast_by_name, var) do + {:ok, v_ast} -> + quote(do: to_string(unquote(v_ast))) + + :error -> + raise ArgumentError, + "#{inspect(__MODULE__)}: unknown variable {#{var}} in :key for " <> + "@#{decorator_type} #{inspect(env_module)}.#{fun_name}/#{length(args_ast)}" + end + end + defp invalid_key!(env_module, name, args, key, decorator_type) do raise ArgumentError, "#{inspect(__MODULE__)}: invalid value #{inspect(key)} in :key " <> diff --git a/mix.exs b/mix.exs index 56209f2..fc91563 100644 --- a/mix.exs +++ b/mix.exs @@ -49,6 +49,7 @@ defmodule CacheDecorator.MixProject do defp deps do [ {:cachex, "~> 4.1", only: :test}, + {:credo, "~> 1.7", only: :dev, runtime: false}, {:ex_doc, "~> 0.38", only: :dev, runtime: false}, {:mockery, "~> 2.3", only: :test} ] diff --git a/mix.lock b/mix.lock index 3e2974a..b2b0c5d 100644 --- a/mix.lock +++ b/mix.lock @@ -1,9 +1,13 @@ %{ + "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "cachex": {:hex, :cachex, "4.1.1", "574c5cd28473db313a0a76aac8c945fe44191659538ca6a1e8946ec300b1a19f", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:ex_hash_ring, "~> 6.0", [hex: :ex_hash_ring, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "d6b7449ff98d6bb92dda58bd4fc3189cae9f99e7042054d669596f56dc503cd8"}, + "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, "eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"}, "ex_doc": {:hex, :ex_doc, "0.38.3", "ddafe36b8e9fe101c093620879f6604f6254861a95133022101c08e75e6c759a", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "ecaa785456a67f63b4e7d7f200e8832fa108279e7eb73fd9928e7e66215a01f9"}, "ex_hash_ring": {:hex, :ex_hash_ring, "6.0.4", "bef9d2d796afbbe25ab5b5a7ed746e06b99c76604f558113c273466d52fa6d6b", [:mix], [], "hexpm", "89adabf31f7d3dfaa36802ce598ce918e9b5b33bae8909ac1a4d052e1e567d18"}, + "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},