You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java编写SonarQube自定义规则:String外部参数switch需做空值检查

SonarQube自定义规则实现:String类型外部参数的switch语句必须做空值检查

需求说明

要实现的规则:当switch语句括号中的变量类型为String且该变量为外部方法参数时,必须先对其进行空值检查。

示例代码

class AvoidSwitchNullRule{
    public void aMethod(String a){
        switch (a){ // 不合规:String类型的外部参数在switch前未做空值检查
            case "1":{
                System.out.println(1);
                break;
            } case "2":{
                System.out.println(2);
                break;
            } default:{
                System.out.println("default");
            }
        }
    }

    public void bMethod(String a) {
        if(StringUtils.isEmpty(a)){
            return;
        }
        switch (a){ // 合规:已提前做空值检查
            case "1":{
                System.out.println(1);
                break;
            } case "2":{
                System.out.println(2);
                break;
            } default:{
                System.out.println("default");
            }
        }
    }
}

未完成的规则代码

@Rule(key = "AvoidSwitchNullRule")
public class AvoidSwitchNullRule extends IssuableSubscriptionVisitor {
    @Override
    public List<Tree.Kind> nodesToVisit() {
        return Arrays.asList(Tree.Kind.SWITCH_STATEMENT);
    }


    @Override
    @ParametersAreNonnullByDefault
    public void visitNode(Tree tree) {
        SwitchStatementTree switchStatementTree = (SwitchStatementTree) tree;
        IdentifierTree identifierTree = (IdentifierTree) switchStatementTree.expression();

        if(identifierTree.symbolType().is("java.lang.String") && identifierTree.name().isEmpty()) {
            this.context.reportIssue(this,tree, "Switch Parameter must not be null");
        }
    }
}

完整实现代码

import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.tree.*;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.Arrays;
import java.util.List;

@Rule(key = "AvoidSwitchNullRule")
public class AvoidSwitchNullRule extends IssuableSubscriptionVisitor {

    @Override
    public List<Tree.Kind> nodesToVisit() {
        return Arrays.asList(Tree.Kind.SWITCH_STATEMENT);
    }

    @Override
    @ParametersAreNonnullByDefault
    public void visitNode(Tree tree) {
        SwitchStatementTree switchStmt = (SwitchStatementTree) tree;
        Tree expr = switchStmt.expression();

        // 仅处理直接使用变量的情况
        if (!(expr instanceof IdentifierTree)) {
            return;
        }

        IdentifierTree paramIdent = (IdentifierTree) expr;
        Symbol symbol = paramIdent.symbol();

        // 判断是否是方法参数且类型为String
        if (!symbol.isParameter() || !paramIdent.symbolType().isString()) {
            return;
        }

        // 检查switch前是否已做空值检查
        if (!hasNullCheckBeforeSwitch(switchStmt, paramIdent.name())) {
            reportIssue(switchStmt, "当switch语句使用String类型的外部参数时,必须先进行空值检查");
        }
    }

    /**
     * 判断switch语句之前是否存在目标参数的空值检查
     */
    private boolean hasNullCheckBeforeSwitch(SwitchStatementTree switchStmt, String paramName) {
        BlockTree enclosingBlock = findEnclosingBlock(switchStmt);
        if (enclosingBlock == null) {
            return false;
        }

        List<? extends StatementTree> statements = enclosingBlock.body();
        int switchIndex = statements.indexOf(switchStmt);
        if (switchIndex <= 0) {
            return false;
        }

        // 遍历switch之前的所有语句
        for (int i = 0; i < switchIndex; i++) {
            StatementTree stmt = statements.get(i);
            if (isNullCheckStatement(stmt, paramName)) {
                return true;
            }
        }

        return false;
    }

    /**
     * 查找switch所在的最内层代码块
     */
    private BlockTree findEnclosingBlock(Tree tree) {
        Tree parent = tree.parent();
        while (parent != null) {
            if (parent instanceof BlockTree) {
                return (BlockTree) parent;
            }
            parent = parent.parent();
        }
        return null;
    }

    /**
     * 判断当前语句是否是目标参数的空值检查
     */
    private boolean isNullCheckStatement(StatementTree stmt, String paramName) {
        if (stmt instanceof IfStatementTree) {
            IfStatementTree ifStmt = (IfStatementTree) stmt;
            return isNullCheckCondition(ifStmt.condition(), paramName);
        }
        // 可扩展处理其他语句类型,比如三元表达式赋值
        return false;
    }

    /**
     * 判断条件表达式是否是目标参数的空值检查
     */
    private boolean isNullCheckCondition(ExpressionTree condition, String paramName) {
        // 处理直接null判断:a == null 或 null == a
        if (condition instanceof BinaryExpressionTree) {
            BinaryExpressionTree binaryExpr = (BinaryExpressionTree) condition;
            if (binaryExpr.is(Tree.Kind.EQUAL_TO)) {
                return (isIdentifier(binaryExpr.leftOperand(), paramName) && isNullLiteral(binaryExpr.rightOperand()))
                        || (isIdentifier(binaryExpr.rightOperand(), paramName) && isNullLiteral(binaryExpr.leftOperand()));
            }
        }

        // 处理工具类方法:StringUtils.isEmpty(a)、Objects.isNull(a)等
        if (condition instanceof MethodInvocationTree) {
            MethodInvocationTree methodInv = (MethodInvocationTree) condition;
            Symbol.MethodSymbol methodSymbol = methodInv.methodSymbol();
            
            boolean isEmptyMethod = methodSymbol.name().equals("isEmpty") 
                    && (methodSymbol.owner().name().equals("StringUtils") || methodSymbol.owner().name().equals("Strings"));
            boolean isNullMethod = methodSymbol.name().equals("isNull") && methodSymbol.owner().name().equals("Objects");
            
            if ((isEmptyMethod || isNullMethod) && !methodInv.arguments().isEmpty()) {
                ExpressionTree arg = methodInv.arguments().get(0);
                return isIdentifier(arg, paramName);
            }
        }

        return false;
    }

    private boolean isIdentifier(ExpressionTree expr, String name) {
        return expr instanceof IdentifierTree && ((IdentifierTree) expr).name().equals(name);
    }

    private boolean isNullLiteral(ExpressionTree expr) {
        return expr instanceof LiteralTree && ((LiteralTree) expr).is(Tree.Kind.NULL_LITERAL);
    }
}

关键实现说明

  1. 参数身份与类型校验:通过symbol.isParameter()确认变量是方法参数,symbolType().isString()快速判断类型为String。
  2. 空值检查识别:
    • 支持直接的null判断语句(如a == null);
    • 支持常见工具类的空检查方法(如StringUtils.isEmpty(a)、Objects.isNull(a)),可根据项目实际使用的工具类扩展判断逻辑;
  3. 范围限定:仅检查switch所在代码块中、位于switch之前的语句,避免误判其他作用域的检查逻辑。

内容的提问来源于stack exchange,提问作者Alex Halawin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 19:51:09