SonarQube 7.8自定义规则插件教程

SonarQube 7.8自定义规则插件教程

网上教程参差不齐,到处copy,各种坑。

另外SonarQube源码里面的demo都是最新版本的,不适用旧版的开发教程,当前7.8的版本已经用了好久,换新版本也不值当。

所有只能自己捣鼓。

在自己折腾一天后,结合网上教程和SonarQube的源码,决定把完整的过程写下来,以免后人捉急

写自定义规则插件

当前的要求是,不允许开发随便修改对外公共接口的任何属性,包括参数名,参数类型,方法名,返回类型,修饰符。

所以就定义一个注解,假设注解名称叫@check,该注解中有个属性叫checkToken。

pom.xml

建一个maven工程,pom文件如下

<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ygy</groupId>
<artifactId>sonar-ygy-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>sonar-plugin</packaging>
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <sonar.version>7.8</sonar.version>
    <!-- this 6.3 is only required to be compliant with SonarLint and it is required even if you just want to be compliant with SonarQube 5.6 -->
    <java.plugin.version>4.7.1.9272</java.plugin.version>
    <sslr.version>1.21</sslr.version>
    <gson.version>2.6.2</gson.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.sonarsource.sonarqube</groupId>
        <artifactId>sonar-plugin-api</artifactId>
        <version>${sonar.version}</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>
    </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>
            <exclusion>
                <groupId>org.codehaus.sonar</groupId>
                <artifactId>sonar-plugin-api</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.codehaus.sonar.sslr</groupId>
                <artifactId>sslr-xpath</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>jcl-over-slf4j</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.sonarsource.java</groupId>
        <artifactId>java-checks-testkit</artifactId>
        <version>${java.plugin.version}</version>
    </dependency>
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>${gson.version}</version>
    </dependency>
    <dependency>
        <groupId>org.sonarsource.sslr</groupId>
        <artifactId>sslr-testing-harness</artifactId>
        <version>${sslr.version}</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.6.2</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
    </dependency>
    <dependency>
        <groupId>org.assertj</groupId>
        <artifactId>assertj-core</artifactId>
        <version>3.6.1</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>0.9.30</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>
            <artifactId>sonar-packaging-maven-plugin</artifactId>
            <version>1.17</version>
            <extensions>true</extensions>
            <configuration>
                <pluginDescription>test</pluginDescription>
                <pluginKey>java-custom</pluginKey>
                <pluginName>Java Custom Rules</pluginName>
                <pluginClass>com.ygy.checks.ygySonarPlugin</pluginClass>   <!-- 这个类就是下文定义的插件类 -->
                <sonarLintSupported>true</sonarLintSupported>
                <sonarQubeMinVersion>7.8</sonarQubeMinVersion> <!-- allow to depend on API 6.x but run on LTS -->
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.6.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.20</version>
            <configuration>
                <skipTests>true</skipTests>
            </configuration>
        </plugin>
    </plugins>
</build>
</project>
自定义规则类
package com.ygy.checks;


import org.apache.commons.lang.StringUtils;
import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.check.Priority;
import org.sonar.check.Rule;
import org.sonar.java.model.declaration.ModifierKeywordTreeImpl;
import org.sonar.java.model.declaration.VariableTreeImpl;
import org.sonar.plugins.java.api.JavaFileScanner;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.tree.*;
import org.sonar.squidbridge.annotations.SqaleConstantRemediation;
import org.sonar.squidbridge.annotations.SqaleSubCharacteristic;

import java.util.Iterator;
import java.util.List;


@Rule(
        // 规则id
        key = "MethodParameterCheck",
        // 规则名称
        name = "接口变更约束",
        // 规则介绍
        description = "对外接口不允许随意修改",
        // 规则标签
        tags = {"card-api-check"},
        // 规则级别
        priority = Priority.CRITICAL)
@SqaleSubCharacteristic(RulesDefinition.SubCharacteristics.ARCHITECTURE_CHANGEABILITY)
@SqaleConstantRemediation("10min") //纠正所需时间
public class MethodParameterCheck extends BaseTreeVisitor implements JavaFileScanner {
    private JavaFileScannerContext context;
    public void scanFile(JavaFileScannerContext context) {
        this.context = context;
        scan(context.getTree());
    }

    @Override
    public void visitMethod( MethodTree tree) {
        String annotationName="check";
        List<AnnotationTree> annotations = tree.modifiers().annotations();

        for (AnnotationTree annotationTree : annotations) {

            TypeTree annotationType = annotationTree.annotationType();

            if (!annotationType.is(Tree.Kind.IDENTIFIER)) continue;

            IdentifierTree identifier = (IdentifierTree) annotationType;
            if (identifier.name().equals(annotationName)) {
                String checkToken="";
                Arguments arguments=annotationTree.arguments();
                Iterator<ExpressionTree> iterator = arguments.iterator();
                while (iterator.hasNext()){
                    ExpressionTree next = iterator.next();
                    String v=next.firstToken().text();
                    if(v.equals("checkToken")){
                        checkToken=next.lastToken().text().replaceAll("\"","").replace("(","").replace(")","").replaceAll(",","");
                        break;
                    }
                }
                if(!checkToken.isEmpty()){
                    checkMethod(tree, identifier, checkToken);
                }
                break;
            }

        }
        super.visitMethod(tree);
    }

    /**
     * 检查方法属性是否变化
     * @param tree
     * @param identifier
     * @param checkToken
     */
    private void checkMethod(MethodTree tree, IdentifierTree identifier, String checkToken) {
        //提取修饰符,返回值,方法名,参数类型,参数名,进行依次拼接
        String methodName=tree.simpleName().name();
        String returnType=tree.symbol().returnType().type().name();
        List<ModifierKeywordTree> mktl= tree.modifiers().modifiers();
        String modifier="";
        for(ModifierKeywordTree mkt:mktl){
            modifier+=((ModifierKeywordTreeImpl)mkt).text();
        }
        String annotationRealValue=modifier+returnType+methodName;
        List<VariableTree> tpl=tree.parameters();
        for(VariableTree vt:tpl){

            String typeName=vt.type().firstToken().text();
            String name=vt.simpleName().name();
            annotationRealValue+=typeName+name;
        }
        annotationRealValue= StringUtils.deleteWhitespace(annotationRealValue);
        if(!annotationRealValue.equals(StringUtils.deleteWhitespace(checkToken))){
            context.reportIssue(this, identifier, String.format("%s该方法不允许修改参数名,参数类型,方法名,返回类型,修饰符", methodName));
        }
    }
}

需要注意的是,因为我当前的要求是检查方法,所以这边重写了visitMethod方法;

如果要检查类,可以重写visitClass;

还有visitImport,顾名思义,检查导入类的。

这些方法都在BaseTreeVisitor这个类中,需要的可以自己进去看。

检查注册类
package com.ygy.checks;

import org.sonar.plugins.java.api.CheckRegistrar;
import org.sonar.plugins.java.api.JavaCheck;

import java.util.Arrays;

public class ygyJavaFileCheckRegistrar implements CheckRegistrar {
    public void register(RegistrarContext registrarContext) {
        registrarContext.registerClassesForRepository(ygyJavaRulesDefinition.REPOSITORY_KEY,
                Arrays.asList(checkClasses()), Arrays.asList(testCheckClasses()));
    }
    public static Class<? extends JavaCheck>[] checkClasses() {
        return  new Class[] {MethodParameterCheck.class};
    }
    @SuppressWarnings("unchecked")
    public static Class<? extends JavaCheck>[] testCheckClasses() {
        return new Class[] {};
    }

}

这个类无需关心,依葫芦画瓢就行了,把上面的注册进行就行

规则列表类
package com.ygy.checks;

import org.sonar.api.internal.google.common.collect.ImmutableList;
import org.sonar.plugins.java.api.JavaCheck;

import java.util.List;

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(MethodParameterCheck.class)
                .build();
    }

    public static List<Class<? extends JavaCheck>> getJavaTestChecks() {
        return ImmutableList.<Class<? extends JavaCheck>>builder()
                .build();
    }

}

同上,依葫芦画瓢,把规则类MethodParameterCheck这个放进去

规则解析类
package com.ygy.checks;

import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.squidbridge.annotations.AnnotationBasedRulesDefinition;

public class ygyJavaRulesDefinition implements RulesDefinition {
    public static final String REPOSITORY_KEY = "ygyRepo";
    public void define(Context context) {
        NewRepository repository = context.createRepository(REPOSITORY_KEY,"java");
        repository.setName("Java编码规范");
        AnnotationBasedRulesDefinition.load(repository, "java",RulesList.getChecks());
        repository.done();
    }
}

没什么好说的,照抄就行了。

插件入口类
package com.ygy.checks;

import org.sonar.api.Plugin;

public class ygySonarPlugin implements Plugin {
    @Override
    public void define(Context context) {
        context.addExtension(ygyJavaRulesDefinition.class);
        context.addExtension(ygyJavaFileCheckRegistrar.class);
    }
}
maven打包
mvn clean package

打包的日志中,会有一些告警,注意看一次

大意是有些jar包,是SonarQube提供的,就不打进来了。这边埋下一个坑。

安装插件

把上面打的jar包放到sonarqube的 extensions\plugins目录下

然后重新启动sonarqube,我这边事windows的,就执行windows目录下的启动命令。当然bin目录下也有其他系统的,比如linux

启动日志在logs目录下

启动如果失败,可以到这里面查看日志。

我就遇到了报各种类不存在,就看了一下这些类在哪个jar包下面,然后直接把这些jar包手动放进插件jar的META-INF\lib下

启用自定义规则

进入sonarqube后台,查看自定义规则是否能找到。进入代码规则菜单,标签里面筛选我们上面MethodParameterCheck类上注解中定义的标签

进入质量配置菜单,过滤一下语言,此处是java。

找到默认的配置,如下图,当前默认的是FindBugs,点击后面的配置按钮

点击激活更多规则

查找到我们自定义的规则,然后点击活动。就ok了

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值