Android Lint插件怎样识别用户是采取了自动修复还是忽略问题?

最近在学习Android Lint插件开发,有一个需求在于,当其他用户使用了我的插件后,对于检测出来的代码问题,怎样识别用户是通过Quick Fix自动修复问题,还是通过添加注解@SuppressLint("xx") 忽略该问题?识别出来之后需要将数据上报进行用户使用情况统计分析。截图如下,也就是在插件检测出代码问题后,识别用户是点击1进行自动修复,还是点击2忽略该问题,我在Lint规则的visitMethodCall方法检测出问题并调用context.report(ISSUE, node, context.getLocation(node), msg, fix)上报issue后,尝试从参数中解析,但好像没法识别:

img

参考GPT和自己的思路:在Android Lint插件中,可以通过检查修复建议(fix)的数量来判断用户是采用了自动修复还是忽略问题。如果修复建议的数量大于0,则说明用户采用了自动修复。如果修复建议的数量为0,则说明用户采用了忽略问题。

可以在LintFix类中使用getAlternatives()方法获取修复建议的数量。例如,在visitMethodCall()方法中,可以按照以下方式检查修复建议的数量:

@Override
public void visitMethodCall(@NonNull JavaContext context, @NonNull UCallExpression node, @NonNull PsiMethod method) {
    // ...
    LintFix fix = LintFix.create().name("MyFix").replace().all().with("newCode").build();
    context.report(ISSUE, node, context.getLocation(node), "My message", fix);

    // Get the number of alternatives
    int fixCount = fix.getAlternatives().size();

    if (fixCount > 0) {
        // The issue was fixed automatically
    } else {
        // The issue was suppressed with an annotation
    }
}


注意,在检查修复建议的数量之前,需要先调用LintFix.create()方法创建修复建议对象。如果修复建议的数量为0,则说明用户采用了忽略问题,可以相应地记录并上报这种情况。

在上报用户使用情况的时候,可以使用一些统计工具,如Google Analytics或Firebase Analytics。可以使用这些工具来收集用户数据并分析用户行为,以优化插件的功能和性能。

参考GPT和自己的思路,在 Android Lint 插件中,要识别用户是采取了自动修复还是忽略问题,可以通过在 fix 参数中添加 SuggestedFix 来实现自动修复。当用户点击 Quick Fix 时,会调用 SuggestedFix.apply() 方法来应用修复建议,从而修复代码问题。而当用户使用 @SuppressLint 注解忽略问题时,不会对代码进行任何自动修复,也不会返回 SuggestedFix 对象。

因此,您可以在 context.report() 方法中判断 fix 参数是否为 null,如果为 null 则说明用户选择了忽略问题,否则说明用户选择了自动修复。下面是示例代码:

@Override
public void visitMethodCall(@NonNull JavaContext context, @NonNull UCallExpression node, @NonNull PsiMethod calledMethod,
                            @Nullable PsiExpression receiver) {
    // 检测代码问题并生成错误信息
    if (hasCodeIssue(node)) {
        String errorMessage = generateErrorMessage(node);

        // 判断用户是否选择了自动修复
        SuggestedFix fix = generateFix(node);
        if (fix != null) {
            // 用户选择了自动修复
            context.report(ISSUE, node, context.getLocation(node), errorMessage, fix);
            // 上报用户使用情况
            reportUsage(context, true);
        } else {
            // 用户选择了忽略问题
            context.report(ISSUE, node, context.getLocation(node), errorMessage);
            // 上报用户使用情况
            reportUsage(context, false);
        }
    }
}

private void reportUsage(@NonNull JavaContext context, boolean isFixed) {
    // TODO: 上报用户使用情况,包括是否修复了问题
}

private boolean hasCodeIssue(@NonNull UCallExpression node) {
    // TODO: 检测代码问题
    return true;
}

private String generateErrorMessage(@NonNull UCallExpression node) {
    // TODO: 生成错误信息
    return "Found a code issue.";
}

private SuggestedFix generateFix(@NonNull UCallExpression node) {
    // TODO: 生成修复建议
    return null;
}

在上述代码中,generateFix() 方法用于生成修复建议,如果检测到代码问题并且能够生成修复建议,则返回 SuggestedFix 对象,否则返回 null。在 visitMethodCall() 方法中,我们根据 fix 参数是否为 null 来判断用户选择了自动修复还是忽略问题,并在 reportUsage() 方法中上报用户使用情况。

在Android Lint插件开发中,您可以通过访问AST树的方式,来判断用户是通过Quick Fix自动修复问题还是通过添加注解@SuppressLint("xx")来忽略该问题。

具体地,您可以在visitAnnotation和visitMethodCall这两个方法中,检查用户使用的注解和方法调用,然后将相关信息记录在一个map中,再在report方法中获取该map信息,进行上报和统计分析。

例如,您可以定义一个类似于下面的map,来记录用户的操作:

private Map<String, String> operationMap = new HashMap<>();


然后在visitAnnotation方法中,检查用户是否添加了@SuppressLint注解:

@Override
public void visitAnnotation(@NonNull UAnnotation annotation) {
    if (annotation.matches("android.annotation.SuppressLint")) {
        operationMap.put(annotation.getParent().toString(), "ignore");
    }
    super.visitAnnotation(annotation);
}


在visitMethodCall方法中,检查用户是否调用了Quick Fix方法:

@Override
public void visitMethodCall(@NonNull UCallExpression call) {
    if (call.getMethodName().equals("apply")) {
        operationMap.put(call.getParent().toString(), "fix");
    }
    super.visitMethodCall(call);
}


最后,在report方法中,获取operationMap信息,并进行上报和统计分析:

@Override
public void report(@NonNull Context context, @NonNull Location location, @NonNull String message, @NonNull Issue.IssueLocation issueLocation, @Nullable LintFix fix) {
    String operation = operationMap.get(location.toString());
    if (operation != null) {
        // 上报和统计分析
    }
    super.report(context, location, message, issueLocation, fix);
}


注意,上述代码仅为示例,实际情况需要根据具体的Lint规则进行修改。

该回答引用ChatGPT

如有疑问,可以回复我!

可以尝试使用以下方法来识别用户是否使用了 Quick Fix 进行自动修复:

1、在 Lint 规则中实现 LintFix 接口,并在 fix 方法中实现自动修复逻辑。
2、在 fix 方法中添加一个自定义的 UserData 对象,用于标识该修复操作是通过 Quick Fix 进行的。
3、在 visitMethodCall 方法中,判断当前报告的 LintError 是否包含自定义的 UserData 对象。如果包含,则说明该问题是通过 Quick Fix 进行了修复。
4、在 visitAnnotation 方法中,判断当前报告的 LintError 是否包含 @SuppressLint 注解。如果包含,则说明用户已经忽略了该问题。

public class MyLintRule extends Detector implements Detector.UastScanner, LintFix {

  private static final String QUICK_FIX_USER_DATA_KEY = "quickFix";

  // ... 省略其他代码

  @Override
  public void visitMethodCall(@NonNull JavaContext context, @NonNull UCallExpression node,
      @NonNull PsiMethod method) {
    // ... 省略其他代码

    // 判断是否需要自动修复
    LintFix fix = getFix(node);
    if (fix != null) {
      // 添加自定义的 UserData
      fix.setData(QUICK_FIX_USER_DATA_KEY, true);

      context.report(ISSUE, node, context.getLocation(node),
          "This issue can be automatically fixed", fix);
    }
  }

  @Nullable
  @Override
  public LintFix getFix(@NonNull Node node) {
    // 实现自动修复逻辑
    // ...

    LintFix fix = LintFix.create()
        .name("Fix this issue")
        .replace()
        .with(replacement)
        .build();

    return fix;
  }

  @Override
  public void apply(@NonNull JavaContext context, @NonNull LintFix fix,
      @NonNull UElement node) {
    // 获取自定义的 UserData
    boolean isQuickFix = fix.getData(QUICK_FIX_USER_DATA_KEY, false);

    if (isQuickFix) {
      // 上报 Quick Fix 统计数据
      // ...
    } else {
      // 上报注解忽略统计数据
      // ...
    }

    // 应用修复
    fix.apply(context, node);
  }

  @Override
  public List<Class<? extends UElement>> getApplicableUastTypes() {
    return ImmutableList.of(UMethod.class);
  }

  @Override
  public UElementHandler createUastHandler(@NonNull JavaContext context) {
    return new UElementHandler() {
      @Override
      public void visitMethod(@NonNull UMethod node) {
        // ... 省略其他代码

        List<Annotation> annotations = node.getAnnotations();
        for (Annotation annotation : annotations) {
          if (annotation instanceof PsiAnnotation) {
            String qualifiedName = ((PsiAnnotation) annotation).getQualifiedName();
            if ("SuppressLint".equals(qualifiedName)) {
              // 判断是否需要忽略该问题
              context.report(ISSUE, node, context.getLocation(node),
                  "This issue can be ignored with the @SuppressLint annotation");
            }
          }
        }
      }
    };
  }
}


以下答案由GPT-3.5大模型与博主波罗歌共同编写:
在Android Lint插件中,可以使用LintFix实例来提供自动修复的建议,使用@SuppressLint("xx")注解来忽略问题。在context.report方法中,可以通过传入修复建议和注解信息来指示用户是采取了哪种方式来处理问题。

下面是一个示例代码:

public class MyDetector extends Detector implements Detector.JavaScanner {

    public static final Issue ISSUE = Issue.create(
            "MyIssue",
            "Description",
            "Explanation",
            Category.CORRECTNESS,
            5,
            Severity.WARNING,
            new Implementation(MyDetector.class, Scope.JAVA_FILE_SCOPE));

    private static final String QUICK_FIX = "qucik fix";
    private static final String SUPPRESS_ANNOTATION = "suppress annotation";

    @Override
    public List<String> getApplicableMethodNames() {
        return Collections.singletonList("setText");
    }

    @Override
    public void visitMethodCall(JavaContext context, UCallExpression call, PsiMethod method) {
        if (context.getEvaluator().isMemberInClass(method, "android.widget.TextView")) {
            String message = "You should use a string resource instead.";
            Location location = context.getLocation(call);
            LintFix fix = LintFix.create()
                    .name("Replace with string resource")
                    .replace()
                    .text("setText(${params:[something]})")
                    .with("setText(R.string.my_string)");

            String action = getUserAction();
            if (action.equals(QUICK_FIX)) {
                context.report(ISSUE, call, location, message, fix);
            } else if (action.equals(SUPPRESS_ANNOTATION)) {
                context.report(ISSUE, call, location, message,
                        LintFix.create().data(SUPPRESS_ANNOTATION));
            }
        }
    }

    private String getUserAction() {
        // Use your own logic to retrieve user action
        return QUICK_FIX;
    }
}

在visitMethodCall方法中,我们可以根据自己的规则检测代码问题,通过LintFix实例提供修复建议。如果用户采取了自动修复,我们调用context.report方法,并传入修复建议;如果用户采取了忽略问题,我们调用context.report方法,并传入一个表示注解信息的LintFix对象。

在getUserAction方法中,你可以使用自己的逻辑来识别用户的操作。

当收集使用情况时,可以遍历上报给Lint提供的Issue,然后对每个问题统计采取自动修复和忽略问题的用户数量。
如果我的回答解决了您的问题,请采纳!

要识别用户是通过Quick Fix自动修复问题,还是通过添加注解@SuppressLint("xx")忽略该问题,可以使用LintFix对象来标记问题修复方式,然后在上报问题时将修复方式一起上报。

在自定义Lint规则中,可以通过LintFix.create()方法创建一个LintFix对象,该对象包含了修复问题所需的信息,例如修复代码、修复描述等。然后,在使用context.report()方法上报问题时,可以将LintFix对象作为第五个参数传入,用于标记问题的修复方式。

示例代码如下:

public class MyDetector extends Detector implements Detector.JavaPsiScanner {

    private static final Implementation IMPLEMENTATION = new Implementation(
            MyDetector.class,
            EnumSet.of(Scope.JAVA_FILE));

    public static final Issue ISSUE = Issue.create(
            "MyId",
            "MyTitle",
            "MyDescription",
            Category.CORRECTNESS,
            5,
            Severity.ERROR,
            IMPLEMENTATION);

    @Override
    public List<String> getApplicableMethodNames() {
        return Collections.singletonList("findViewById");
    }

    @Override
    public void visitMethodCall(@NonNull JavaContext context, @NonNull UCallExpression node,
                                @NonNull PsiMethodCallExpression call) {
        // 检测到问题后,创建修复问题所需的信息
        LintFix fix = LintFix.create()
                .replace()
                .name("Replace with binding")
                .range(context.getLocation(call))
                .text("findViewById")
                .with("binding");

        // 上报问题时,将修复方式一起上报
        context.report(ISSUE, node, context.getLocation(node), "My message", fix);
    }
}

在以上示例代码中,LintFix.create()方法创建了一个LintFix对象,并使用replace()方法指定了修复方式。在上报问题时,将LintFix对象作为第五个参数传入,用于标记问题的修复方式。

然后,在统计用户使用情况时,可以通过问题的Location对象来获取用户的操作方式。Location对象包含了问题的位置信息,可以通过context.getLocation()方法获取。然后,可以通过Location#getSecondary()方法获取修复方式的描述信息,用于统计用户使用情况。

示例代码如下:

public void run(@NonNull Context context) {
    List<Issue> issues = Arrays.asList(MyDetector.ISSUE);
    List<Warning> warnings = new ArrayList<>();

    Project project = createProject(context);
    LintDriver driver = new LintDriver(new IssueRegistry(issues), new LintRequest(project), createLintClient(warnings));
    List<LintIssue> lintIssues = driver.run();

    for (LintIssue issue : lintIssues) {
        Location location = issue.getLocation();
        String secondary = location.getSecondary().getDescription();
        if (secondary.contains("Replace with binding")) {
            // 用户使用了Quick Fix进行修复
            // 进行上报数据统计分析
        } else if (secondary.contains("Suppress with annotation")) {
            // 用户使用了注解进行忽略
            // 进行上报数据统计分析
        }
    }
}

在以上示例代码中,Location#getSecondary()方法获取修复方式的描述信息,并使用`String

您好,根据您的描述,我觉得您可以尝试在 visitMethodCall 方法中,通过检查 fix.data 是否存在来判断用户是通过Quick Fix自动修复问题还是通过添加注解来忽略该问题。

如果 fix.data 存在,那么可以认为用户是通过Quick Fix自动修复问题,可以将这个信息上报给服务器进行统计分析;否则,就可以认为用户是通过添加注解来忽略该问题,也可以将这个信息上报给服务器进行统计分析。

具体代码实现如下:

@Override
public void visitMethodCall(@NonNull JavaContext context, @NonNull UCallExpression node,
                            @NonNull PsiMethodCallExpression expression, @NonNull Issue issue) {
    ...
    LintFix fix = LintFix.create().data(myPluginData);
    context.report(issue, node, context.getLocation(node), message, fix);
}

@Override
public void afterCheckProject(@NonNull Context context) {
    for (Map.Entry<Project, Map<Object, LintFix.Data>> entry : myData.entrySet()) {
        Project project = entry.getKey();
        Map<Object, LintFix.Data> dataMap = entry.getValue();
        for (Map.Entry<Object, LintFix.Data> dataEntry : dataMap.entrySet()) {
            Object location = dataEntry.getKey();
            LintFix.Data data = dataEntry.getValue();
            // 判断用户是否通过Quick Fix自动修复问题
            if (data != null && data.getQuickfixData() != null) {
                // 上报数据进行统计分析
                reportData(project, location, true);
            } else {
                // 上报数据进行统计分析
                reportData(project, location, false);
            }
        }
    }
}

需要注意的是,需要将 fix.data 设置为可序列化的数据对象,以便在 afterCheckProject 方法中提取并进行分析。如果您仍然无法解决问题,请提供更为详细的代码和错误信息,以便我们更好地帮助您解决问题。

你先了解一下这个配置:

companion object {
    /**
     * Creates a new issue. The description strings can use some simple markup;
     * see the [TextFormat.RAW] documentation
     * for details.
     *
     * @param id the fixed id of the issue
     * @param briefDescription short summary (typically 5-6 words or less), typically
     * describing the **problem** rather than the **fix**
     * (e.g. "Missing minSdkVersion")
     * @param explanation a full explanation of the issue, with suggestions for
     * how to fix it
     * @param category the associated category, if any
     * @param priority the priority, a number from 1 to 10 with 10 being most
     * important/severe
     * @param severity the default severity of the issue
     * @param implementation the default implementation for this issue
     * @return a new [Issue]
     */
    @JvmStatic
    fun create(
        id: String,
        briefDescription: String,
        explanation: String,
        category: Category,
        priority: Int,
        severity: Severity,
        implementation: Implementation
    ): Issue {
        val platforms = computePlatforms(null, implementation)
        return Issue(
            id, briefDescription, explanation, category, priority,
            severity, platforms, null, implementation
        )
    }
}


参数id 唯一的id,简要表面当前提示的问题。
参数briefDescription 简单描述当前问题
参数explanation 详细解释当前问题和修复建议
参数category 问题类别
参数priority 优先级,从11010最重要
参数Severity 严重程度:FATAL(奔溃), ERROR(错误), WARNING(警告),INFORMATIONAL(信息性),IGNORE(可忽略)
参数Implementation Issue和哪个Detector绑定,以及声明检查的范围。Scope有如下选择范围:RESOURCE_FILE(资源文件),BINARY_RESOURCE_FILE(二进制资源文件),RESOURCE_FOLDER(资源文件夹),ALL_RESOURCE_FILES(所有资源文件),JAVA_FILE(Java文件), ALL_JAVA_FILES(所有Java文件),CLASS_FILE(class文件), ALL_CLASS_FILES(所有class文件),MANIFEST(配置清单文件), PROGUARD_FILE(混淆文件),JAVA_LIBRARIES(Java库), GRADLE_FILE(Gradle文件),PROPERTY_FILE(属性文件),TEST_SOURCES(测试资源),OTHER(其他);