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了