credo is great.

Every single Elixir project all around should use it. Besides fancy mix task to report project styling issues, it has bindings to most of modern editors, like neovim, emacs through alchemist.el and even Atom.

The greatest thing about credo is it provides the meaningful shell return codes, making it quite easy to plug it as a linter into git hooks toolchain. Let’s do it:

Include credo dependency into mix.exs project file

  defp deps do
    [
      ...
      {:credo, "~> 0.7", only: [:dev, :test]},
      ...
    ]
  end

if you are reading this article from the future, immediately run

$ mix deps.update credo

to get the up-to-date version. It’s safe, since we’d use it in non-prod environments only. Let’s try it in action.

Running credo for the first time

$ mix credo

Checking 36 source files ...

  Software Design
┃
┃ [D] β†— Found a FIXME tag in a comment: # FIXME FIXME FIXME
┃       lib/my_app/file1.ex:194 (MyApp.File1.fun1)
┃ [D] β†’ Found a TODO tag in a comment: # Todo: should round here?
┃       lib/my_app/file2.ex:53 (MyApp.File2.fun2)

  Code Readability                                                              
┃
┃ [R] β†’ Modules should have a @moduledoc tag.
┃       lib/my_app/file1.ex:4:13 (MyApp.File1)
┃ [R] β†’ Modules should have a @moduledoc tag.
┃       lib/my_app/file3.ex:3:11 (MyApp.File3)

  Refactoring opportunities                                                     
┃
┃ [F] β†’ Function body is nested too deep (max depth is 2, was 4).
┃       lib/my_app/file1.ex:179:21 (MyApp.File1.fun1)

  Warnings - please take a look                                                 
┃
┃ [W] β†— Prefer lazy Logger calls.
┃       lib/my_app/file1.ex:91 (MyApp.File1.fun3)
┃ [W] β†— Prefer lazy Logger calls.
┃       lib/my_app/file1.ex:82 (MyApp.File1.fun2)
┃ [W] β†— Prefer lazy Logger calls.
┃       lib/my_app/file1.ex:169 (MyApp.File1.fun3)

Please report incorrect results: https://github.com/rrrene/credo/issues

Analysis took 1.1 seconds (0.02s to load, 1.1s running checks)
220 mods/funs, found 3 warnings,
                     1 refactoring opportunity,
                     2 code readability issues,
                     2 software design suggestions.

Here we go. First run report is always depressive and driving me bonkers. I was writing this code today morning, it’s, you know, kinda perfect. I hope you’ll get zero warnings and at most one TODO tag found, but I never get there. First run turns me into a couple of hours of refactoring. The good thing is from now on it’s fairly easy to keep the project in clean state, that satisfies even such a captious judge as credo.

Configure credo

The very straightforward way to make everything tuned would be to:

cd config
# wget https://raw.githubusercontent.com/rrrene/credo/master/.credo.exs
mix credo gen.config
vim .credo.exs
cd -

The file is self-documented. Just go tweak it.

Making it happen recurrently

Well, I knew one guy who had put running linters into crontab at 2AM. He started every morning with few cups of coffee and several hours of refactoring. I am not as brave. I use git hooks to lint my code (save for editor plugins.)

Let’s configure our .git/config in the first place. Just add the following section to it:

[credo]
  terminate = 16

The above will terminate the commit when there are warnings. Check how credo calculates an exit status for details.

OK, now we are to create a hook itself. You might just put this in your .git/hooks/pre-commit local hook (create this file if it’s not existing yet):

#!/bin/sh
#
# Linter Elixir files using Credo.
# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
#
# Config
# ------
# credo.terminate
#   The credo exit status level to be considered as β€œnot passed”—to prevent
#   git commit until fixed.

# Config
terminate_on=$(git config --int credo.terminate)
if [[ -z "$terminate_on" ]]; then terminate_on=16; fi

# test it :: run tests before commit (silently)
mix test 2>&1 >/dev/null
TEST_RES=$?
if [ $TEST_RES -ne 0 ]
then
  echo ""
  echo "β˜† ==================================== β˜†"
	echo "β˜†  Some tests are failed.              β˜†" >&2
	echo "β˜†  Please fix them before committing.  β˜†" >&2
  echo "β˜† ==================================== β˜†"
  echo ""
  exit $TEST_RES
fi
echo ""
echo "β˜… ============================== β˜…"
echo "β˜…   Tests passed successfully.   β˜…"
echo "β˜… ============================== β˜…"
echo ""

# lint it :: credo checks before commit
mix credo
CREDO_RES=$?
if [ $CREDO_RES -ge $(terminate_on) ]; then
  echo ""
  echo "β˜† ============================================= β˜†"
	echo "β˜† Credo found critical problems with your code  β˜†" >&2
	echo "β˜†   and commit can not proceed. Please examine  β˜†" >&2
	echo "β˜†   log above and fix issues before committing. β˜†" >&2
  echo "β˜† ============================================= β˜†"
  echo ""
  exit $CREDO_RES
fi
if [ $CREDO_RES -le 9 ]; then CREDO_RES=" $CREDO_RES"; fi
echo ""
echo "β˜… ============================== β˜…"
echo "β˜…   Credo passed successfully.   β˜…"
echo "β˜…   Exit value is: (total: $CREDO_RES).  β˜…"
echo "β˜… ============================== β˜…"
echo ""

# Finished
exit 0

Before credo we also run tests. If you are like me and want to commit code even while all the tests are failing (hopefully providing the comment like β€œ:bomb: [BROKEN] Evening commit! Do not use at home or school!”,) just supply --no-verify option to git commit.

And yes, we are done. Try to git commit and you’ll see as your code will be tested and credo’ed for you automagically.