본문 바로가기
프로그래밍 놀이터/안드로이드, Java

[android] custom lint 만들기

by 돼지왕 왕돼지 2020. 7. 31.
반응형


android custom lint


http://jeremie-martinez.com/2015/12/15/custom-lint-rules


왜 custom lint 를 만들어야 하는가?

1. librady/SDK 를 개발할 때 custom lint 를 넣어주면, 사용자들이 제대로 사용하도록 가이드를 하는 역할을 한다.

2. 프로젝트 진행시 새로운 개발자에게 convention 을 자연스럽게 전달할 수 있다.




예제를 통해 배워보자.


Custom Lint 는 새로운 module 로 정의되어야만 한다.



Gradle


-

apply plugin: 'java'


targetCompatibility = JavaVersion.VERSION_1_7

sourceCompatibility = JavaVersion.VERSION_1_7


configurations {

    lintChecks

}


dependencies {

    compile 'com.android.tools.lint:lint-api:25.3.0'

    compile 'com.android.tools.lint:lint-checks:25.3.0'


    lintChecks files(jar)

}


jar {

    manifest {

        attributes('Lint-Registry': 'com.cklee.lint.MyCustomLintRegistry')

    }

}


defaultTasks 'assemble'


task install(type: Copy, dependsOn: build) {

    from configurations.lintChecks

    into System.getProperty('user.home') + '/.android/lint/'

}



-

compile 하고 deploy 하려면 아래 명령어를 사용한다.

$ gradlew clean install




예제 1 : Custom Attr 는 prefix 가 있어야 한다.


-

public class AttrPrefixDetector extends ResourceXmlDetector {

private static final String PREFIX = “ck”;

 public static final Issue ISSUE = Issue.create("AttrNotPrefixed”, // id, must be unique
        "You must prefix your custom attr by `” + PREFIX + “‘", // brief desc
        "We prefix all our attrs to avoid clashes.”, // detailed desc
        Category.TYPOGRAPHY, // category
        5, // priority, 1~10
        Severity.WARNING, // severity
        new Implementation(AttrPrefixDetector.class, Scope.RESOURCE_FILE_SCOPE)); // bridge b/w Detector & Scope

 // Only XML files
 @Override
 public boolean appliesTo(@NonNull Context context, @NonNull File file) {
   return LintUtils.isXmlFile(file);
 }

// Only values folder
 @Override
 public boolean appliesTo(ResourceFolderType folderType) {
    return ResourceFolderType.VALUES == folderType;
}

// Only attr tag
 @Override
 public Collection<String> getApplicableElements() {
    return Collections.singletonList(TAG_ATTR);
 }

// Only name attribute
 @Override
 public Collection<String> getApplicableAttributes() {
    return Collections.singletonList(ATTR_NAME);
 }

 @Override
 public void visitElement(XmlContext context, Element element) {
    final Attr attributeNode = element.getAttributeNode(ATTR_NAME);
    if (attributeNode != null) {
        final String val = attributeNode.getValue();
        if (val.startsWith("android:") == false && val.startsWith(PREFIX) == false) {
            context.report(ISSUE,
                    attributeNode,
                    context.getLocation(attributeNode),
                    "You must prefix your custom attr by `" + PREFIX + "`");
        }
    }
 }
}



예제 2.  production 에서 Log 금지


-

public class LoggerUsageDetector extends Detector implements Detector.ClassScanner {

    public static final Issue ISSUE = Issue.create("LogUtilsNotUsed",
            "You must use our `LogUtils`",
            "Logging should be avoided in production for security and performance reasons. Therefore, we created a LogUtils that wraps all our calls to Logger and disable them for release flavor.",
            Category.MESSAGES,
            9,
            Severity.ERROR,
            new Implementation(LoggerUsageDetector.class,
                                Scope.CLASS_FILE_SCOPE));

    @Override
    public List<String> getApplicableCallNames() {
        return Arrays.asList("v", "d", "i", "w", "e", "wtf");
    }

    @Override
    public List<String> getApplicableMethodNames() {
        return Arrays.asList("v", "d", "i", "w", "e", "wtf");
    }

    @Override
    public void checkCall(@NonNull ClassContext context,
                          @NonNull ClassNode classNode,
                          @NonNull MethodNode method,
                          @NonNull MethodInsnNode call) {
        String owner = call.owner;
        if (owner.startsWith("android/util/Log")) {
            context.report(ISSUE,
                           method,
                           call,
                           context.getLocation(call),
                           "You must use our `LogUtils`");
        }
    }
}


등록


-

public final class MyCustomLintRegistry extends IssueRegistry {
    @Override
    public List<Issue> getIssues() {
        return Arrays.asList(LoggerUsageDetector.ISSUE, AttrPrefixDetector.ISSUE);
    }
}



-

끝!


느낌은 저정도이고, 제대로 구현하려면 ref doc 을 잘 참조하자!





반응형

댓글