Clean the Lint Out

One of the nicer tools a developer has is the linter. The linter helps you conform to a clean, unified and easy to read format of your code. When I join a project that has no automated lint set up, adding lint to the flow is one of the first steps I take.

In the past, I used to set up lint on the build server. That way, no pull request (PR) would get merged which breaks the formatting rules of the project. However, this had a few flaws. First, unless squashed, your git history had lint fix commits all over. Second, it would really slow you down as a developer. You’d create a PR, and then wait for a few minutes – only for the build server to tell you your formatting is wrong.

It was only fairly recently that I learned about git hooks. In a project I joined, they were using them in the pre-push step to prevent you from pushing code that was not properly linted. Pretty cool. But we could do better. If we moved linting to the pre-commit step, you couldn’t even commit a file that was not linted. Now, you don’t even need a separate commit for the lint fixes.

The linting script on that project was also scanning the whole project. This works really well when your project is already linted. However, if you’re setting this up on an existing project, you could never commit anything without fixing the whole project. To fix that, I rewrote the lint script to only iterate just over the newly staged files. For Macs, the script looks like this:

#!/bin/bash

echo "Running lint..."

git diff --name-only --cached | grep "\.kt" | while read fn; do ktlint "${fn}"; done
RESULT_KTLINT=$?

[ $RESULT_KTLINT -ne 0 ] && exit 1
exit 0

For Windows, it looks like this:

#!C:/Program\ Files/Git/usr/bin/sh.exe

echo "Running lint..."

git diff --name-only --cached | grep "\.kt" | while read fn; do ktlint "${fn}"; done
RESULT_KTLINT=$?

[ $RESULT_KTLINT -ne 0 ] && exit 1
exit 0

The above examples run ktlint, for Kotlin projects. You should be able to port them to any other language fairly easily.

There’s still one problem. Every developer has to copy the relevant script manually to their .git/hooks folder (and name them “pre-commit”). This can be solved by automating the copying task. In gradle, it can be done by updating your app build.gradle like so:

task installGitHook(type: Copy) {
    def suffix = "macos"
    if (Os.isFamily(Os.FAMILY_WINDOWS)) {
        suffix = "windows"
    }
    from new File(rootProject.rootDir, "automation/scripts/pre-commit-$suffix")
    into { new File(rootProject.rootDir, '.git/hooks') }
    rename("pre-commit-$suffix", 'pre-commit')
    fileMode 0775
}

tasks.getByPath(':app:preBuild').dependsOn installGitHook

And voila! The first time a developer builds the project, the appropriate git hook gets installed. Don’t you just love automation?

Published by eranboudjnah

A software consultant and tech lead. Passionate about optimizing as many aspects of my life as possible, to free time for what really matters.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: