I wrote my own static scan demo

Introduction to the

Android Lint is a code scanning tool introduced with SDK Tools 16 (ADT 16). It can help developers find code quality issues and suggest improvements by statically analyzing code. In addition to checking the Android project source code for potential errors, the code is also checked for correctness, security, performance, ease of use, convenience, and internationalization.

Recently, in the process of project development, I found that it is difficult to promote the process of code update iteration without providing some corresponding tools to the business side if we want to promote the docking work of some middleware. So I’m going to use the Lint static code checking tool to mark up code on the business side where we want to improve, and then help them modify and upgrade the business code based on the error message.

Lint configuration

For configuration related to performing Lint operations, these are defined in the lintOptions of the Gradle file, with definable options and their default values

android {
    lintOptions {
        // Set to true to stop the Gradle build when Lint finds an error
        abortOnError false
        // If set to true, the full or absolute path of the file will be displayed in case of an error (true by default)
        absolutePaths true
        // Check only the specified problem (specified by id)
        check 'NewApi'.'InlinedApi'
        // Set to true to check for all problems, including default not to check for problems
        checkAllWarnings true
        // When set to true, release builds will run Lint at Fatal.
        // If a Fatal problem is found during build, the build will be aborted (controlled by abortOnError).
        checkReleaseBuilds true
        // Do not check the specified problem (specified by the problem ID)
        disable 'TypographyFractions'.'TypographyQuotes'
        // Check the specified problem (specified by id)
        enable 'RtlHardcoded'.'RtlCompat'.'RtlEnabled'
        // Whether to return the corresponding Lint description in the report
        explainIssues true
        // The path to write the report, which defaults to lint-results.html in the build directory
        htmlOutput file("lint-report.html")
        // Set to true to generate an HTML report
        htmlReport true
        // Set to true to report only errors
        ignoreWarnings true
        // Respecify the Lint rule configuration file
        lintConfig file("default-lint.xml")
        // If true, the source code line number is not included in the error report
        noLines true
        Lint will not report the progress of an analysis when set to true
        quiet true
        // Override the severity of the Lint rule, for example:
        severityOverrides ["MissingTranslation": LintOptions.SEVERITY_WARNING]
        // Set to true to show all the places where a problem is, without truncating the list
        showAll true
        // Configure the location of the output to be written. The format can be file or stdout
        textOutput 'stdout'
        // Set to true to generate a plain text report (default: false)
        textReport false
        // If this parameter is set to true, all warnings will be treated as errors
        warningsAsErrors true
        // Write the check report to the file (not specified by default lint-results.xml)
        xmlOutput file("lint-report.xml")
        // Set to true to generate an XML report
        xmlReport false
        // Set the severity of the specified problem (specified by ID) to Fatal
        fatal 'NewApi', 'InlineApi'
        // Set the severity of the specified problem (specified by ID) to Error
        error 'Wakelock', 'TextViewEdits'
        // Set the severity of the specified problem (specified by ID) to Warning
        warning 'ResourceAsColor'
        // Set the severity of the specified problem (specified by ID) to ignore
        ignore 'TypographyQuotes'
    }
}
Copy the code

** Lint checks has been updated to version v2, so the configuration items must be filled in according to the following rules.

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    compileOnly 'com. Android. Tools. Lint: lint - API: 26.4.2'
    compileOnly 'com. Android. Tools. Lint: lint - checks: 26.4.2'
}

sourceCompatibility = "1.8"
targetCompatibility = "1.8"

jar {
    manifest {
        attributes("Lint-Registry-v2": "com.xxx.lint.router.=IssueRegistry")}}Copy the code

The key point is that attributes “lint-registry-v2” must be written dead. Value is the IssueRegistry within the project.

How to implement a custom Lint rule

Let me briefly introduce the Detector class. The first thing that needs to be clear about this class check is what we’re checking the first time, so it’s very simple and very clear, which class I’m checking, whether it’s code or a resource file or XML, whether it’s a constructor or a method call. The second point is which boundary conditions we think are problematic and where to throw an exception to alert the developer.

public class RouteDetector extends Detector implements Detector.UastScanner {
    private final String WM_ROUTER_PACKAGE = "com.sankuai.waimai.router";
    private final String WM_ROUTER_ANNOTATION = WM_ROUTER_PACKAGE + ".annotation.RouterPage";
    private final String WM_ROUTER_CALL = WM_ROUTER_PACKAGE + ".Router";
    static final Issue ISSUE = Issue.create(
            "router_annotation_issue"./ / the only ID
            "This annotation is not allowed.".// Brief description
            "Global items do not allow this annotation. Replace the RouterUri.".// Detailed description
            Category.CORRECTNESS,   // Problem type (correctness, safety, etc.)
            6./ / weight
            Severity.ERROR,   // Severity of the problem (ignore, warning, error)
            new Implementation(     // Implementation, including processing instances and scopes
                    RouteDetector.class,
                    Scope.JAVA_FILE_SCOPE));
    static final Issue CALL_ISSUE = ISSUE.create("router_call_issue"./ / the only ID
            "Do not refer directly to WM router".// Brief description
            "Use project encapsulated routing middleware to complete the jump.".// Detailed description
            Category.CORRECTNESS,   // Problem type (correctness, safety, etc.)
            6./ / weight
            Severity.ERROR,   // Severity of the problem (ignore, warning, error)
            new Implementation(     // Implementation, including processing instances and scopes
                    RouteDetector.class,
                    Scope.JAVA_FILE_SCOPE));
	
    @Override
    public List<Class<? extends UElement>> getApplicableUastTypes() {
        List<Class<? extends UElement>> types = new ArrayList<>();
        types.add(UAnnotation.class);
        types.add(UCallExpression.class);
        return types;
    }

    @Override
    public UElementHandler createUastHandler(@NotNull JavaContext context) {
        return new UElementHandler() {

            @Override
            public void visitAnnotation(@NotNull UAnnotation node) {
                isAnnotation(node);
            }

            private void isAnnotation(UAnnotation node) {
                String type = node.getQualifiedName();
                if (WM_ROUTER_ANNOTATION.equals(type)) {
                    context.report(ISSUE, node, context.getLocation(node),
                            "This annotation is not allowed."); }}@Override
            public void visitCallExpression(@NotNull UCallExpression node) {
                checkIsMethod(node);
                checkIsConstructorCall(node);
            }

            private void checkIsConstructorCall(UCallExpression node) {
                if(! UastExpressionUtils.isConstructorCall(node)) {return;
                }
                UReferenceExpression classRef = node.getClassReference();
                if(classRef ! =null) {
                    String className = UastUtils.getQualifiedName(classRef);
                    String value = "com.xxx.routerprotocol.request.xxxUriRequest";
                    String uriValue = WM_ROUTER_PACKAGE + ".common.DefaultUriRequest";
                    String pageValue = WM_ROUTER_PACKAGE + ".common.DefaultPageUriRequest";
                    if (className.equals(value)) {
                        context.report(CALL_ISSUE, node, context.getLocation(node),
                                "Please use the routing middleware provided by the project");
                    }
                    if (className.equals(uriValue) || className.equals(pageValue)) {
                        context.report(CALL_ISSUE, node, context.getLocation(node),
                                "Please use the routing middleware provided by the project"); }}}private void checkIsMethod(UCallExpression node) {
                if (UastExpressionUtils.isMethodCall(node)) {
                    if(node.getReceiver() ! =null&& node.getMethodName() ! =null) {
                        PsiMethod method = node.resolve();
                        if (context.getEvaluator().isMemberInClass(method, WM_ROUTER_CALL)) {
                            context.report(CALL_ISSUE, node, context.getLocation(node),
                                    "Please use the routing middleware provided by the project"); }}}}}; }}Copy the code

This is the block of code that project Lint checks. The main core point is getApplicableUastTypes. You can allow the type to be passed in before the UElementHandler below can be intercepted so that code checks can be developed.

UCallExpression is a type that accepts constructors and method calls in code, and if there is a particular class or object that you don’t want to allow business people to use, you can add an error to it.

One thing to note about UElementHandler is that you do not want to write super where you want to do a check.

What this code does is scan the code to see if the RouterPager annotation is implemented. It then checks to see if the Router class is referenced in the project. If the project is using an expired UriRequest or Meituan DefaultUriRequest, an issue will be thrown if referenced.

Lint can also scan XML files, resource files, and Gradle files, making static scanning a great way to help build android projects.

Let’s examine another lint for resource file checking


import com.android.resources.ResourceFolderType;
import com.android.tools.lint.detector.api.*;
import org.jetbrains.annotations.NotNull;

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

public class PngResourceDetector extends Detector implements Detector.ResourceFolderScanner {

    public static final Issue ISSUE = Issue.create(
            "image too large"."Log Usage"."Please use the unified LogUtil class!",
            Category.CORRECTNESS,
            6,
            Severity.ERROR,
            new Implementation(PngResourceDetector.class, Scope.RESOURCE_FOLDER_SCOPE)
    );


    @Override
    public boolean appliesToResourceRefs() {
        return super.appliesToResourceRefs();
    }


    @Override
    public void checkFolder(@NotNull ResourceContext context, @NotNull String folderName) {
        super.checkFolder(context, folderName);
        File parent = context.file;
        for (File file : parent.listFiles()) {
            if (file.isFile()) {
                long length = file.length();
                System.out.print(file.toString());
                if (length > 100) {
                    context.report(ISSUE, Location.create(file),
                            "This code mentions `lint`: **Congratulations**");
                }
            }
        }

    }

    @Override
    public boolean appliesTo(@NotNull ResourceFolderType folderType) {
        System.out.print(folderType.name());
        // return true;
        returnfolderType.compareTo(ResourceFolderType.DRAWABLE) == 0 || folderType.compareTo(ResourceFolderType.MIPMAP) == 0; }}Copy the code

What does the above code do and what resources do you want to check and you need to implement the interface, so if I want to check Java Kotlin code, then you need to implement UastScanner, if you want to scan resource files, then you need to implement ResourceFolderScanner. Lint will only scan mipmap and Drawable, so all we need to do is scan those two types. Then we go into the checkFolder method, so what we’re doing right now is simply checking the length of the file, and we’ll get an error if the file size is greater than what.