Introduction
SonarQube is very popular and powerful tool for static code analysis. This tool can analyze multiple languages like Java, C#, JavaScript, PL/SQL and many more. By default SonarQube provides lot of rules to analyze your code. However, sometimes you might need to write your own rule as per your need. This blog covers how to write the custom rules and package them as sonar plugin.
Suppose, the lenth of variable name less than 4 characters does not follow the coding guideline for your company. Then you might need to write custom rules to achieve this. Below example shows how to achieve this.
Requeired Software
- JDK 1.8
- Eclipse for J2EE
- Maven 3.x or above
- SonarQube 5.6
- sonar-runner-dist 2.4
Stepts to write code
- Create a simple maven project with the following files (described below). Build the code and place the jar into **extensions\plugins folder in SonarQube.
SL No | Class/File Name | Description |
---|---|---|
1 | pom.xml | Required dependencies are added in this file. The packaging type of the plugin is sonar-plugin. Sonar plugin version is 3.13.1 in this case. |
2 | com.ashish.custom.sonar.java.plugin.CustomJavaRulesEntry | This class is the entry point for the SONAR plugin. This class is extended from org.sonar.api.SonarPlugin class. This class includes server extension which gets instanciated during sonarqube startup and batch extensions which gets instantiated during the code analysis. |
3 | com.ashish.custom.sonar.java.plugin.CustomRulesDefinition | This class is a Server extension which gets instanciated at the time of sonarqube startup. The repository name and supported language name is mentioned in this class |
4 | com.ashish.custom.sonar.java.plugin.CustomJavaFileCheckRegistrar | This class is the batch extension which gets instanciated during the code analysis. This class registers all custom rule classes. |
5 | com.ashish.custom.sonar.java.plugin.RulesList | This class lists all custom rules and provides the list to the CustomJavaFileCheckRegistrar class to register them with sonarqube |
6 | com.ashish.custom.sonar.java.rules.AvoidSmallerLengthVariableNameRule | This is the sample custom rule that I have implemented in this example i.e. the lenth of the variable name should be more than 4 characters long. |
pom.xml : Packaging type is sonar-plugin
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ashish.sonar.custom.java</groupId>
<artifactId>java-custom-rules</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>sonar-plugin</packaging>
<properties>
<java.plugin.version>3.13.1</java.plugin.version>
</properties>
<name>Java Custom Rules</name>
<description>Java Custom Rules</description>
<dependencies>
<dependency>
<groupId>org.codehaus.sonar</groupId>
<artifactId>sonar-plugin-api</artifactId>
<version>4.5.6</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.sonarsource.java</groupId>
<artifactId>sonar-java-plugin</artifactId>
<type>sonar-plugin</type>
<version>${java.plugin.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.sonarsource.java</groupId>
<artifactId>java-frontend</artifactId>
<version>${java.plugin.version}</version>
</dependency>
<dependency>
<groupId>org.sonarsource.sslr-squid-bridge</groupId>
<artifactId>sslr-squid-bridge</artifactId>
<version>2.6.1</version>
<exclusions>
<exclusion>
<groupId>org.codehaus.sonar.sslr</groupId>
<artifactId>sslr-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.sonarsource.java</groupId>
<artifactId>java-checks-testkit</artifactId>
<version>${java.plugin.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.sonar.sslr</groupId>
<artifactId>sslr-testing-harness</artifactId>
<version>1.19.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easytesting</groupId>
<artifactId>fest-assert</artifactId>
<version>1.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>0.9.30</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>
<artifactId>sonar-packaging-maven-plugin</artifactId>
<version>1.15</version>
<extensions>true</extensions>
<configuration>
<pluginClass>com.ashish.custom.sonar.java.plugin.CustomJavaRulesEntry</pluginClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.10</version>
<executions>
<execution>
<id>copy</id>
<phase>test-compile</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
<type>jar</type>
</artifactItem>
</artifactItems>
<outputDirectory>${project.build.directory}/test-jars</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
- com.ashish.custom.sonar.java.plugin.CustomJavaRulesEntry : This class is the entry point for the SONAR plugin. This class is extended from org.sonar.api.SonarPlugin class. This class includes server extension which gets instanciated during sonarqube startup and batch extensions which gets instantiated during the code analysis.
package com.ashish.custom.sonar.java.plugin;
import java.util.Arrays;
import java.util.List;
import org.sonar.api.SonarPlugin;
/*********************************
* Entry point of the sonar plugin
********************************/
public class CustomJavaRulesEntry extends SonarPlugin {
@Override
public List getExtensions() {
return Arrays.asList(
// server extensions -> objects are instantiated during sonarqube startup
CustomRulesDefinition.class,
// batch extensions -> objects are instantiated during the code analysis
CustomJavaFileCheckRegistrar.class);
}
}
- com.ashish.custom.sonar.java.plugin.CustomRulesDefinition: This class is a Server extension which gets instanciated at the time of sonarqube startup. The repository name and supported language name is mentioned in this class
package com.ashish.custom.sonar.java.plugin;
import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.plugins.java.Java;
import org.sonar.squidbridge.annotations.AnnotationBasedRulesDefinition;
/**
* Declare rule metadata in server repository of rules.
* That allows to list the rules in the page "Rules".
*/
public class CustomRulesDefinition implements RulesDefinition {
public static final String REPOSITORY_KEY = "customRepo";
@Override
public void define(Context context) {
NewRepository repository = context.createRepository(REPOSITORY_KEY, Java.KEY);
repository.setName("CustomRepo");
AnnotationBasedRulesDefinition.load(repository, "java", RulesList.getChecks());
repository.done();
}
}
- com.ashish.custom.sonar.java.plugin.CustomJavaFileCheckRegistrar: This class is the batch extension which gets instanciated during the code analysis. This class registers all custom rule classes.
package com.ashish.custom.sonar.java.plugin;
import java.util.Arrays;
import org.sonar.plugins.java.api.CheckRegistrar;
import org.sonar.plugins.java.api.JavaCheck;
import com.ashish.custom.sonar.java.rules.AvoidSmallerLengthVariableNameRule;
/**
* Provide the "checks" (implementations of rules) classes that are going to get executed during the source code analysis.
*
* This class is a batch extension by implementing the {@link org.sonar.plugins.java.api.CheckRegistrar} interface.
*/
public class CustomJavaFileCheckRegistrar implements CheckRegistrar {
/**
* Register the classes that will be used to instantiate checks during analysis.
*/
@Override
public void register(RegistrarContext registrarContext) {
// Call to registerClassesForRepository to associate the classes with the correct repository key
registrarContext.registerClassesForRepository(CustomRulesDefinition.REPOSITORY_KEY, Arrays.asList(checkClasses()), Arrays.asList(testCheckClasses()));
}
/**
* Lists all the checks provided by the plugin
*/
public static Class<? extends JavaCheck>[] checkClasses() {
return new Class[] { // List of rules to be included here
AvoidSmallerLengthVariableNameRule.class
};
}
/**
* Lists all the test checks provided by the plugin
*/
public static Class<? extends JavaCheck>[] testCheckClasses() {
return new Class[] {};
}
}
- com.ashish.custom.sonar.java.plugin.RulesList: This class lists all custom rules and provides the list to the CustomJavaFileCheckRegistrar class to register them with sonarqube
package com.ashish.custom.sonar.java.plugin;
import java.util.List;
import org.sonar.plugins.java.api.JavaCheck;
import com.ashish.custom.sonar.java.rules.AvoidSmallerLengthVariableNameRule;
import com.google.common.collect.ImmutableList;
public final class RulesList {
private RulesList() {
}
public static List<Class> getChecks() {
return ImmutableList.<Class>builder().addAll(getJavaChecks()).addAll(getJavaTestChecks()).build();
}
public static List<Class<? extends JavaCheck>> getJavaChecks() {
return ImmutableList.<Class<? extends JavaCheck>>builder()
.add(AvoidSmallerLengthVariableNameRule.class)
.build();
}
public static List<Class<? extends JavaCheck>> getJavaTestChecks() {
return ImmutableList.<Class<? extends JavaCheck>>builder()
.build();
}
}
- com.ashish.custom.sonar.java.rules.AvoidSmallerLengthVariableNameRule: This is the sample custom rule that I have implemented in this example i.e. the lenth of the variable name should be more than 4 characters long. The rule is tagged with coding-guideline and the priority of this rule is MINOR
package com.ashish.custom.sonar.java.rules;
import java.util.List;
import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.check.Priority;
import org.sonar.check.Rule;
import org.sonar.check.RuleProperty;
import org.sonar.plugins.java.api.JavaFileScanner;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
import org.sonar.plugins.java.api.tree.MethodTree;
import org.sonar.plugins.java.api.tree.VariableTree;
import org.sonar.squidbridge.annotations.SqaleConstantRemediation;
import org.sonar.squidbridge.annotations.SqaleSubCharacteristic;
@Rule(key = "AvoidSmallerLengthLocalVariableName",
name = "Avoid usage of the smaller length in local variable name",
description = "This rule detects usage of smaller length local variable name. Variable name should not be smaller than 4 characters.",
tags = {"coding-guideline"},
priority = Priority.MINOR)
@SqaleSubCharacteristic(RulesDefinition.SubCharacteristics.ARCHITECTURE_CHANGEABILITY)
@SqaleConstantRemediation("10min")
public class AvoidSmallerLengthVariableNameRule extends BaseTreeVisitor implements JavaFileScanner {
private static final String DEFAULT_VALUE = "SmallerLengthLocalVariable";
private JavaFileScannerContext context;
/**
* Avoid usage of the smaller length in local variable name in Quality profiles.
* The key
*/
@RuleProperty(
defaultValue = DEFAULT_VALUE,
description = "Avoid usage of the smaller length in local variable name")
protected String name;
@Override
public void scanFile(JavaFileScannerContext context) {
this.context = context;
scan(context.getTree());
}
@Override
public void visitVariable(VariableTree tree) {
String variableName = tree.simpleName().name();
System.out.println("Scanning the variable : " + variableName);
if(variableName.length() < 4) {
context.addIssue(tree, this, "Variable length is less than 4 characters");
}
super.visitVariable(tree);
}
}
Build and deployment
Build the maven application and place the Jar into %SONAR_QUBE_PATH%\extensions\plugins location.
Now, open the SonarQube application in browser->Go to rules->Select CustomRepo-> You would be able to see the rule that has been written in this application as shown below