Skip to main content

Detekt

What is Detekt?

Detekt is a static code analysis tool for Kotlin. It is the tool used by Narbase to implement coding standards.  Detekt comes with a wide range of rule sets. Detekt provides options to configure them, as well as the ability to add our own custom rules. 

Quick Guide

  1. Run ./gradlew detekt to run detekt on the whole project.
  2. Alternativly, run detek on a subproject ex -  :narcore-web:detekt
  3. Detekt is run every commit attempt, which is aborted upon failure of the detekt task.
  4. When working on a new project from the 'narcore' stater project, make sure to modify the run commands in the scripts/pre-commit file.

Add Detekt To a Project

This section is a guide to adding the detekt plugin to a new project and configuring it. This is also useful to determine what changes to make, and where, when starting from the narcore starter project. 

Add Detekt Plugin

If starting a new project, add the detekt plugin to the build.gradle.kts of the root directory. Then apply the plugin to all projects.

allprojects {
  ...
    apply(plugin = "io.gitlab.arturbosch.detekt")
}
plugins {
    id("io.gitlab.arturbosch.detekt").version("1.22.0")
}

The next step is to set up a configuration file. Detekt allows you to generate default configurations by running the Gradle task detektGenerateConfig. This generates a detekt.yml file in the 'detekt' directory within the 'config' directory. This will have the default detekt configurations. You are then free to adjust these configurations to company coding standards.

If starting a new project, copy the Narbase configurations from an existing Narbase project. However, all projects are generally started from 'narcore', so this is rarely necessary. For more about rules that may be configured for detekt, read the official documentation.

In most instances, you will be working on an ongoing project and the above process will already have been completed.

Pre-commit Git Hook

Now you should be able to manually run detekt on any of the sub-projects to analyze the code. However, in order to enforce coding standards, detekt must run on the entire project every time a team member attempts to commit new changes. This is implemented with the help of Git Hooks.

Git hooks are scripts that Git executes before or after events such as: commitpush, and receive. Git hooks are a built-in feature - no need to download anything. Git hooks are run locally.

Every Git repository has a .git/hooks folder with a script for each hook you can bind to. By default, these have a ".sample" extension. Remove the extension from the file name which you wish to make executable.  The names of the script files are descriptive of when they are executed, and Git will execute them when those events occur.

 Replace the sample code in the 'pre-commit' hook file with the bash script below. Now attempt to commit new changes. Detekt should run, and if failed, the commit should fail.

#!/usr/bin/env bash
echo "Running detekt check..."
OUTPUT="/tmp/detekt-$(date +%s)"
./gradlew detekt > $OUTPUT
EXIT_CODE1=$?
if [ $EXIT_CODE1 -ne 0 ]; then
  cat $OUTPUT
  rm $OUTPUT
  echo "***********************************************"
  echo "                 Detekt failed                 "
  echo " Please fix the above issues before committing "
  echo "***********************************************"
fi
rm $OUTPUT

echo "Running detekt check on web-dto..."
OUTPUT="/tmp/detekt-$(date +%s)"
./gradlew :dto-web:detekt > $OUTPUT
EXIT_CODE2=$?
if [ $EXIT_CODE2 -ne 0 ]; then
  cat $OUTPUT
  rm $OUTPUT
  echo "***********************************************"
  echo "                 Detekt failed                 "
  echo " Please fix the above issues before committing "
  echo "***********************************************"
fi
rm $OUTPUT

echo "Running detekt check on narcore-web..."
OUTPUT="/tmp/detekt-$(date +%s)"
./gradlew :narcore-web:detekt > $OUTPUT
EXIT_CODE3=$?
EXIT_CODE=$EXIT_CODE1 && $EXIT_CODE2 && $EXIT_CODE3
if [ $EXIT_CODE3 -ne 0 ]; then
  cat $OUTPUT
  rm $OUTPUT
  echo "***********************************************"
  echo "                 Detekt failed                 "
  echo " Please fix the above issues before committing "
  echo "***********************************************"
  exit $EXIT_CODE
fi
exit $EXIT_CODE
rm $OUTPUT

 With the above pre-commit hook, the detekt task runs on each of the sub-subprojects on every attempt at a commit. In the event that detekt fails on any of the sub-project, the commit is aborted. When starting from a narcore starter project, remember to modify the commit to the correct project names. 

As mentioned earlier, git hooks work locally but are not pushed to the remote directory. With the current state of the project, every team member must add manually add the pre-commit hook to their local repository when joining the project. The goal of using a linting tool is to institutionalize coding standards. Therefore, this is counterintuitive as it adds a human element, by trusting each developer to add the scripts. This risk is eliminated by creating a Gradle task to copy and add the hook upon every build.

First, create a 'scripts' directory in the project's root directory. Move the 'pre-commit' script from '.git/hooks' here. Then add the following task to the root 'build.gradle.kts'.

tasks.register("copyHook") {
    File("./scripts/pre-commit").copyTo(
        File("./.git/hooks/pre-commit"),
        overwrite = true
    )
    mustRunAfter("build")
}

Add a Basline

 When adding detekt in the middle of a project, the existing issues may be too many that and fixing them will be too time-consuming. In this instance, run the detektBaseline task. This generates detekt-basline.xml files in each subproject with existing issues. 

Suppress Warning

In the instance where it is inevitable for a rule to be broken, we may use the @Suppress annotation to ignore a warning. Keep in mind that this is not a recommended or permanent solution. For example, let's say a method must remain this long for some reason. This is how to bypass the warning. Use the exact name of the warning, and more tan one warning name can be added.

@Suppress("LongMethod")
fun migrate(targetVersion: String? = null) {
	...
}