Custom Lint для Java / Android Report, если мы находим вызов класса без реализации его интерфейса

Я пишу пользовательский детектор пуха для удовольствия. Я ухожу с урока Big Nerd Ranches по созданию настраиваемого правила lint в Android (должно быть то же самое для Java)

Я могу обнаружить случай, который я хочу. Это конструктор класса был назван. Однако из-за того, что мы пересекаем абстрактное синтаксическое дерево, я не могу обнаружить реализацию обратного вызова. Я не уверен, как сказать Java проверять файл и сообщать, только если он не может найти вхождение. В этом случае реализация интерфейса. Поскольку я вижу только один лист за раз.

Как мне найти два вхождения, сохранить места, где произошло каждое вхождение, а затем выполнить мою логику и составить соответствующий отчет?

package com.bignerdranch.linette.detectors;

import com.android.annotations.NonNull;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.JavaContext;
import com.android.tools.lint.detector.api.Location;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.TextFormat;

import java.io.File;
import java.util.EnumSet;
import java.util.List;

import lombok.ast.AstVisitor;
import lombok.ast.Node;

/**
 * Lint check for the usage of to-do statements
 */
public class CallBackDetector extends Detector implements Detector.JavaScanner {

    private static final String FRAGMENT_MATCHER_STRING = "NoInternetDialogFragment()";
    private static final String INTERFACE_MATCHER_STRING =
            "NoInternetDialogFragment.NoInternetCallbackInterface";

    private static final Class<? extends Detector> DETECTOR_CLASS = CallBackDetector.class;
    private static final EnumSet<Scope> DETECTOR_SCOPE = Scope.JAVA_FILE_SCOPE;

    private static final Implementation IMPLEMENTATION = new Implementation(
            DETECTOR_CLASS,
            DETECTOR_SCOPE
    );

    private static final String ISSUE_ID = "NoInternetDialogFragment";
    private static final String ISSUE_DESCRIPTION =
            "NoInternetDialogFragment Callback not detected";
    private static final String ISSUE_EXPLANATION =
            "When using NoInternetDialogFragment you must implement its' callback -- "
                    + "NoInternetCallbackInterface";
    private static final Category ISSUE_CATEGORY = Category.CORRECTNESS;
    private static final int ISSUE_PRIORITY = 10;
    private static final Severity ISSUE_SEVERITY = Severity.ERROR;

    public static final Issue ISSUE = Issue.create(
            ISSUE_ID,
            ISSUE_DESCRIPTION,
            ISSUE_EXPLANATION,
            ISSUE_CATEGORY,
            ISSUE_PRIORITY,
            ISSUE_SEVERITY,
            IMPLEMENTATION
    );

    /**
     * Constructs a new {@link CallBackDetector} check
     */
    public CallBackDetector() {
    }

    @Override
    public boolean appliesTo(@NonNull Context context, @NonNull File file) {
        return true;
    }

    @Override
    public List<Class<? extends Node>> getApplicableNodeTypes() {
        return null;
    }

    @Override
    public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
        String source = context.getContents();

        // Check validity of source
        if (source == null) {
            return null;
        }

        if(source.indexOf(INTERFACE_MATCHER_STRING) >=0){
            return null;
        }
        int index = source.indexOf(FRAGMENT_MATCHER_STRING);

        for (int i = index; i >= 0; i = source.indexOf(FRAGMENT_MATCHER_STRING, i + 1)) {
            Location location = Location.create(context.file, source, i,
                    i + FRAGMENT_MATCHER_STRING.length());
            context.report(ISSUE, location, ISSUE.getBriefDescription(TextFormat.TEXT));
        }
        return null;
    }

}

1 ответ

Это возможно, если вы сохраните ссылки, которые вы найдете в виде поля, и сообщите о них в конце всего обхода синтаксиса:

внутри createJavaVisitor сделайте проверку, как у вас сейчас, для использования конструктора. Вместо того, чтобы сообщать о проблеме на этом этапе, создайте поле сбора в вашем CallBackDetector это будет содержать ссылку на использование конструктора. Затем вы получите список всех использований конструктора.

Теперь и внутри createJavaVisitor сделайте проверку для любого класса, реализующего интерфейс, который вы ищете, и добавьте его в другую коллекцию полей.

Вы можете переопределить метод afterCheckProject ( API здесь) в вашем Detector знать, когда это закончится. В этом методе итерируйте две ваши коллекции и удалите все элементы из обеих коллекций, которые являются совпадающей парой (конструктор и реализуемый интерфейс). Все, что осталось в любой коллекции, является ошибками Lint, и вы можете добавить их в качестве проблем на этом этапе.

Другие вопросы по тегам