Use `credo` Linter in Git `pre-commit` Hook
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.