credo_checks

Hex.pmBuild and TestElixir Quality ChecksCode coverage

A collection of opinionated Credo checks aimed at improving code quality and catching common mistakes in Elixir, Oban, and LiveView.

These are checks used internally by Jump's engineering team, but which may not be suitable (or desired) for contribution upstream to mainline Credo.

Available checks

See the individual modules for detailed descriptions of each check type.

Installation and configuration

The following instructions assume you already have Credo configured and working on your codebase.

  1. Add jump_credo_checks to your mix.exs dependencies:

     def deps do
       [
         {:jump_credo_checks, "~> 0.1", only: [:dev], runtime: false},
       ]
     end
  2. Run $ mix deps.get to download the package

  3. Add the desired Credo checks to your .credo.exs:

     %{
       configs: [
         %{
           checks: %{
             enabled: [
               {Jump.CredoChecks.AssertElementSelectorCanNeverFail, []},
               {Jump.CredoChecks.AvoidFunctionLevelElse, []},
               {Jump.CredoChecks.AvoidLoggerConfigureInTest, []},
               # Default exclusion list is empty
               {Jump.CredoChecks.AvoidSocketAssignsInTest, excluded: ["test/app_web/plugs/"]},
               {Jump.CredoChecks.DoctestIExExamples, [
                 # Tells Credo where to look for the `doctest` call.
                 # If you colocate your test files with your implementation, this would just
                 # be `&String.replace_trailing(&1, ".ex", "_test.exs")`
                 derive_test_path: fn filename ->
                   filename
                   |> String.replace_leading("lib/", "test/")
                   |> String.replace_trailing(".ex", "_test.exs")
                 end
               ]},
               {Jump.CredoChecks.ForbiddenFunction, 
                functions: [
                  {:erlang, :binary_to_term, "Use Plug.Crypto.non_executable_binary_to_term/2 instead."},
                ]},
               {Jump.CredoChecks.LiveViewFormCanBeRehydrated, excluded: ["lib/my_app/"]},
               # Default start_after is "0"
               {Jump.CredoChecks.PreferTextColumns, start_after: "20240101000000"},
               {Jump.CredoChecks.TestHasNoAssertions, custom_assertion_functions: [:await_has, :await_with_timeout]},
               # Default max_assertions is 20
               {Jump.CredoChecks.TooManyAssertions, [max_assertions: 20]},
               {Jump.CredoChecks.TopLevelAliasImportRequire, []},
               {Jump.CredoChecks.UseObanProWorker, []},
               {Jump.CredoChecks.VacuousTest,
                 [
                   # When true (default), tests that destructure setup context 
                   # (3-arity test blocks) are considered not vacuous. 
                   # Set to false to check them too.
                   ignore_setup_only_tests?: false,
                   # Additional library namespaces whose calls should not count
                   # as production code. Defaults to []
                   library_modules: [
                     Ecto,
                     Jason,
                     Oban,
                     Phoenix,
                     Plug
                   ]
                 ]},
               {Jump.CredoChecks.WeakAssertion, []},
               # ...
             ]
           }
         }
       ]
     }

Philosophy

We use Credo checks primarily as just-in-time education for encouraging best practices. While education like this is valuable, the developer experience is strictly worse than making such education entirely unnecessary; for instance, by:

Thus, we don't use Credo in cases where we can instead use Quokka (or Quokka plugins) to automatically rewrite code. For instance, there's no need to bug developers with a Credo check that asks them to rewrite Enum.map(...) |> Enum.into(%{}) to instead use Map.new/2; we can rewrite that automatically and never need the education.

A request for you, the user

If you use these checks, please give us feedback—which checks were valuable for you, and which weren't?

You're welcome to file an issue with your feedback, or contact Tyler Young, one of the maintainers, directly via email, on BlueSky, or on Mastodon.

Contribution guidelines

We welcome pull requests, but be aware that if the proposed checks/changes aren't something we'd want to use in the Jump codebase, we'll politely decline them. (Feel free to open an issue to discuss and get conceptual agreement before doing the work if you'd like.)