Static Code Analysis Without SonarQube

Tomas Bjerre
4 min readJun 4, 2023

It seems that everywhere I go, SonarQube is being used for static code analysis. I will try to explain the fundamental problem with this product and hopefully convince you to not use it!

Lets start with a source repository with 3 branches (main, feat1 and feat2) and some static code analysis tools being applied to those branches.

Branches in a repository mapping to static code analysis tools and rules.
Branches in a repository mapping to static code analysis tools and rules.

I would argue that if you take static code analysis seriously you will want to fulfill these requirements:

  1. Builds should fail whenever any tool finds problems within the code.
    Do not rely on manual processes where developers “should” do things, but instead run the analysis and fail builds whenever problems are found.
  2. Tools, and rules within those tools, should be iterated.
    You should remove rules that you find pointless, add new rules that you find helpful.
  3. It should be possible to do the analysis even when you do not have network access.
    You should not need proxies and/or firewall configurations in order to do the analysis. This is often the case within an organization. Adding a dependency on a server that needs to be reachable, and always online, is a fragile thing in a build process.
    I can see how this might be considered nice to have.

If you use SonarQube you will have the option to use quality profiles. It lets you define what rules to use for the languages in the code. There is also Clean as Code that can enforce other rules to new code.

You can easily fulfill requirement 1 with SonarQube.

Regarding requirement 2, What happens when you add a new rule? The builds on the branches will start failing, even though nothing has changed in those branches. Therefore does not fulfill requirement 2. This problem becomes very clear as your number of repositories grow and more people work on those repositories. Having branches failing like this will likely lead to clashes between developers.

Also SonarQube adds a dependency on a Sonar Server to the build process. And therefore does not fulfill requirement 3.

Ok, so what is a better way of doing static code analysis? Short answer: run the tools locally and let them fail the build whenever problems are found.

I am mostly working with Java and Gradle, or Maven. The tools I mostly use are:

  • Checkstyle
  • PMD
  • Spotbugs

Each of these tools can be configured to fail the build when they find problems. I think it is nice to let all tools do their analysis and when everything is done I get a single report with all problems found.

I implemented Violations Gradle Plugin (and also Violations Maven Plugin) that you can configure to parse the report files of your tools and it will provide a nicely formatted output, in the console, with all problems found.

se/bjurr/gitchangelog/internal/model/ParsedIssue.java
| | | | | |
| Reporter | Rule | Severity | Line | Message |
| | | | | |
+----------+--------------------------------+----------+------+------------------------------+
| | | | | |
| Spotbugs | EQ_GETCLASS_AND_CLASS_CONSTANT | INFO | 91 | equals method fails for |
| | | | | subtypes <p> This class has |
| | | | | an equals method that will |
| | | | | be broken if it is inherited |
| | | | | by subclasses. It compares a |
| | | | | class literal with the class |
| | | | | of the argument (e.g., in |
| | | | | class <code>Foo</code> it |
| | | | | might check if |
| | | | | <code>Foo.class == |
| | | | | o.getClass()</code>). It is |
| | | | | better to check if |
| | | | | <code>this.getClass() == |
| | | | | o.getClass()</code>. </p> |
| | | | | |
+----------+--------------------------------+----------+------+------------------------------+
Summary of se/bjurr/gitchangelog/internal/model/ParsedIssue.java
| | | | | |
| Reporter | INFO | WARN | ERROR | Total |
| | | | | |
+----------+------+------+-------+-------+
| | | | | |
| Spotbugs | 1 | 0 | 0 | 1 |
| | | | | |
+----------+------+------+-------+-------+
| | | | | |
| | 1 | 0 | 0 | 1 |
| | | | | |
+----------+------+------+-------+-------+

Requirement 1 is fulfilled with the a configuration option maxViolations that can be set to 0 and have the build fail.

Requirement 2 is fulfilled by sharing the configuration. If I use Gradle, I package this plugin as well as all tools and rules within a jar. I call it Gradle Scripts. It contains:

Every repository can depend on an exact version of the tools and rules.

buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'se.bjurr.gradle:gradle-scripts:2.22.0'
}
}
apply from: project.buildscript.classLoader.getResource('main.gradle').toURI()

If I change the tools, or rules, I:

If you use the Maven Plugin you can do the same thing with a BOM or parent POM.

Requirement 3 is also fulfilled, this does not add a dependency on any external server.

--

--