Sonar is a code analyser performing quality checks on various languages. It is composed of different code analysers: CheckStyle, PMD, FindBugs and Squid. Each of them provides its own set of rules and Sonar integrates those various reports in a unified web interface.
Internally, we have what we call the tech watch day where we investigate a library and/or a technology during a day with a concrete objective. This article aims to explain what we have tried to integrate custom rules in Sonar. We choose CheckStyle because we have quickly found documentation on how to write rules and there is also a nice IntelliJ plugin to run rules inside the IDE. We do not pretend it was the best option, but a first trial.
CheckStyle uses its own API on top of AST. The analysis is based on the source code and is limited to the class itself. It means that we cannot create a constraint based on the source code of multiple classes. For instance, if a class A must implement an interface I, A must directly implement this interface; A extending B where B implements I is not supported. This is a major drawback, but for the rules we intended to implement this was not blocking.
The project hosted on github has the following modules:
- A reusable tester for CheckStyle rules,
- Some CheckStyle utilities,
- One sample implementation of a CheckStyle rule that does not depend on Sonar at all,
- A module that generates a sonar extension plugin based on that rule,
- A integration test that runs a sample project against the created rule.
Checkstyle Tester
This module provides a tester performing a CheckStyle analysis inside a (unit) test. After that it helps to easily write assertions on the result. The example below shows how the rule UnusedImportsCheck provided out-of-the-box by CheckStyle could be tested. Assume that we run this rule against a class that imports java.util.Date and does not use it. The unused import is located at line 3, column 8.
@Test public void classWithUnusedImport() { getAnalysis().get(ClassWithUnusedImport.class).assertOnlyThose( violation(UnusedImportsCheck.class) .on(3, 8) .withMessage("Unused import - java.util.Date.") .withLevel(SeverityLevel.ERROR) ); }
The tester also allows to test localized messages. In the snippet below we test the French message:
@Test public void classWithUnusedImportFrench() { final CheckStyleTester analysis = CheckStyleTesterBuilder.forConfigFile("/checkstyle-config.xml") .withSourceLocationAsProperty().withLocale(Locale.FRENCH).build().assertNoException(); analysis.get(ClassWithUnusedImport.class).assertOnlyThose( violation(UnusedImportsCheck.class) .on(3, 8) .withMessage("Import inutilisé - java.util.Date.") .withLevel(SeverityLevel.ERROR) ); }
Custom Rule
Now we are able to test a CheckStyle rule, it is about time to create a new rule using some of the utilities exposed in the com.bsb.common.integration.checkstyle-utils module. Let’s implement a rule (ImplementClausesCounterCheck) that checks that a given class does not implement more than two interfaces. Since we have a single potential violation, let’s extend from AbstractSingleCheck. An instance of the check class is created once per analysis regardless of the number of classes to be analysed. The check class is called back when a Java file starts to be processed and when the analysis on this file is finished. There is also a callback when a tree node of interest is encountered. A node can be the declaration of a class, method, or a statement like an implements, extends, etc.
In this case, it means that when a class definition is found, the rule is called back. The rule simply asks for the implements statement and the number of identifiers in it, that is the number of implemented interfaces. If the maximum is reached, a message is logged.
@Override public void finishTree(DetailAST def) { super.finishTree(def); if (!isConstraintSatisfied()) { log(getViolationLine(), getViolationColumn(), getViolationMessage()); } }
The rule is tested against a set of sample classes representing various use cases: one with no implement, one with a number of implements just before the maximum, a class with a violation and a class with an inner class. There are also translation tests that validates the proper message is set in English and French.
This project is also configured to generate a JAR file containing the project and its dependencies. Its purpose is to be usable by the IntelliJ plugin checkstyle-idea. The screenshots bellow describe how to configure the plugin.




Sonar Plugin
The CheckStyle rule can be now exposed as a Sonar plugin. The following description is based on the Sonar documentation page. An XML file contains the declaration of the new Sonar rule (description, id, severity, etc). The class CheckStyleExtensionRepository is responsible of providing Sonar rules. The repository loads the XML file and loads the CheckStyle rule as a Sonar rule. CheckStyleExtensionPlugin declares the extension and specifies the provider to use.
The POM file of this module can install the extension in your Sonar instance. For that, it expects that the directory containing your Sonar installation can be accessed by the file system. The command simply copies the JAR to the path specified in the main POM file (see the sonar.dir property).
You can either change the value of sonar.dir in the POM itself or override it on the command line. For instance, to install the plugin in c:\tools\sonar:
bsblabs@bsblabs ~/sonar-rule-showcase/samples/com.bsb.samples.sonar.implements-checker-plugin $ mvn clean install -PinstallPlugin -Dsonar.dir=c:\tools\sonar
Sonar Integration Tests Module
The integration tests module (not part of the Maven reactor) is configured to analyse the samples written to test the rule in Sonar itself. Of course it expects that the Sonar extension has been previously deployed. The properties that Sonar need are located in the POM file. The analysis is simply performed during the package phase:
bsblabs@bsblabs ~/sonar-rule-showcase/samples/com.bsb.samples.sonar.implements-checker-plugin-its $ mvn clean package
During this phase, the Maven Checkstyle Plugin also performs an analysis. This analysis is completely independent from the Sonar analysis. The only purpose is to show how a new rule can be tested by this plugin. The generated reports are available in the target directory of that module. Of course, the report contains the same violations as the ones displayed by Sonar.




Note that this automated process is optional. When you want to quickly test a rule that you are changing this process might be handy. But you can of course copy the plugin and analyse one of your projects through the Sonar interface as well.
Conclusion
This article briefly describe how one could implement and test a CheckStyle rule and define it as a Sonar plugin. The project available on github describes the different steps to achieve that goal and contains reusable classes to write and test CheckStyle rules.