Sonar custom rule

  sonar

Sonar does not simply display the results of different code inspection tools (e.g. FindBugs, PMD, etc.) directly on the Web page, but reprocesses these results through different plug-ins, and measures the changes in code quality through quantitative methods, so as to conveniently manage the code quality of projects of different sizes and types.

Sonarqubue’s default rules for java testing are not necessarily suitable for us, so we can customize the rules ourselves.

Disable rules

Rules-Quality Profile- Sonar way Java
Viewing the activated rules, you can disable or change the Severity level.
图片描述

Custom rules

Sonar provides XPath or Java extensions. Some languages support XPath and some only support Java. For example, Java only supports Java extensions. For details, seeSupport of Custom Rules by Language

The steps are as follows:

  • Create a SonarQube plug-in

  • Increase dependency

  • Create custom rules

  • Generate jar packages for plug-ins

  • Place the jar package in the sonarqube _ home/extensions/plugins directory.

  • Restart SonarQube

Add maven dependency

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>

        <sonar-plugin-api.version>5.6</sonar-plugin-api.version>
        <sonar-java-plugin.version>4.2</sonar-java-plugin.version>
        <sonar-packaging-maven-plugin.version>1.17</sonar-packaging-maven-plugin.version>

        <sslr-testing-harness.version>1.20</sslr-testing-harness.version>
        <junit.version>4.12</junit.version>
        <fest-assert.version>1.4</fest-assert.version>
        <logback-classic.version>1.1.3</logback-classic.version>

    </properties>
    <dependencies>
        <dependency>
            <groupId>org.sonarsource.sonarqube</groupId>
            <artifactId>sonar-plugin-api</artifactId>
            <version>${sonar-plugin-api.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.sonarsource.java</groupId>
            <artifactId>sonar-java-plugin</artifactId>
            <version>${sonar-java-plugin.version}</version>
            <type>sonar-plugin</type>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.sonarsource.java</groupId>
            <artifactId>java-checks-testkit</artifactId>
            <version>${sonar-java-plugin.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.codehaus.sonar.sslr</groupId>
            <artifactId>sslr-testing-harness</artifactId>
            <version>${sslr-testing-harness.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.easytesting</groupId>
            <artifactId>fest-assert</artifactId>
            <version>${fest-assert.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback-classic.version}</version>
            <scope>test</scope>
        </dependency>

        <!--http://stackoverflow.com/questions/35369058/custom-plugin-for-sonarqube-5-2-produces-noclassdeffounderror-->
        <!-- https://mvnrepository.com/artifact/org.codehaus.sonar.sslr-squid-bridge/sslr-squid-bridge -->
        <dependency>
            <groupId>org.codehaus.sonar.sslr-squid-bridge</groupId>
            <artifactId>sslr-squid-bridge</artifactId>
            <version>2.6</version>
            <!--记得exclude掉sonar-plugin-api,才可以加载其他相关依赖-->
            <exclusions>
                <exclusion>
                    <groupId>org.codehaus.sonar</groupId>
                    <artifactId>sonar-plugin-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.6.2</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>19.0</version>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.10</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>


    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>
                <artifactId>sonar-packaging-maven-plugin</artifactId>
                <version>${sonar-packaging-maven-plugin.version}</version>
                <extensions>true</extensions>
                <configuration>
                    <pluginKey>java-custom</pluginKey>
                    <pluginName>Java Custom Rules</pluginName>
                    <!-- your plugin class -->
                    <pluginClass>com.xixicat.sonar.MySonarPlugin</pluginClass>
                    <sonarLintSupported>true</sonarLintSupported>
                    <sonarQubeMinVersion>5.6</sonarQubeMinVersion> <!-- allow to depend on API 6.x but run on LTS -->
                </configuration>
            </plugin>
        </plugins>
    </build>

The dependencies here should be carefully configured. Remember to exclude sonar-plugin-api before loading other related dependencies, and then display the jar used by the dependencies. Otherwise, it is easy to report class not found

Writing plugin

Create plugin portal

public class MySonarPlugin implements Plugin {
    public void define(Context context) {
// server extensions -> objects are instantiated during server startup
        context.addExtension(MyJavaRulesDefinition.class);

        // batch extensions -> objects are instantiated during code analysis
        context.addExtension(MyJavaFileCheckRegistrar.class);
    }
}

This class implements the org.sonar.api.Plugin interface and mainly adds two types of extensions:

  • server extensions
    Instantiate at startup of sonarqube server to implement org.sonar.api.server.rule.rulesdefinition interface

public class MyJavaRulesDefinition implements RulesDefinition {

    public static final String REPOSITORY_KEY = "myRepo";

    public void define(Context context) {
        NewRepository repository = context.createRepository(REPOSITORY_KEY, Java.KEY);
        repository.setName("my sonar repo");
        AnnotationBasedRulesDefinition.load(repository, "java", RulesList.getChecks());
        repository.done();
    }
}

public 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();
    }
}
  • batch extensions
    Instantiate when analyzing the code and implement the org.sonar.plugins.java.api.checkregister interface

public class MyJavaFileCheckRegistrar implements CheckRegistrar {
    public void register(RegistrarContext registrarContext) {
        // Call to registerClassesForRepository to associate the classes with the correct repository key
        registrarContext.registerClassesForRepository(MyJavaRulesDefinition.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[] {};
    }
}

Create rule

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.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)
//SQALE全称是Software Quality Assessment based on Lifecycle Expectations,是一套评估代码质量的方法。
@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;

    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.reportIssue(this,tree, "Variable length is less than 4 characters");
        }
        super.visitVariable(tree);
    }
}

Packing plugin

mvn clean package sonar-packaging:sonar-plugin

Copy to plugins

cp target/sonar-rule-demo-0.0.1-SNAPSHOT.jar ~/sonar/extensions/plugins

run

docker run --rm  \
 -e JAVA_OPTS='-Xmx1g' \
  -v /Users/xixicat/sonar/data:/opt/sonarqube/data \
  -v /Users/xixicat/sonar/extensions:/opt/sonarqube/extensions \
  -p 9000:9000 -p 9092:9092 \
  -e SONARQUBE_JDBC_USERNAME=sonar \
  -e SONARQUBE_JDBC_PASSWORD=sonar \
  sonarqube:lts-alpine

View custom rules

图片描述

doc