본문 바로가기
Android개발

Android Lint #2 - Custom Lint

by 궝테스트 2020. 7. 3.

본인 프로젝트에 맞는 Lint 구성을 직접 코딩해서 만들 수 있다!
우선 새로운 프로젝트 또는 기존 프로젝트의 모듈로 Custom lint 를 만들어보자.
(참고로 module 명은 lint 로 설정함)

1. lint module 의 build.gradle 구성

dependency 에 com.android.tools.lint 에서 제공하는 lint-api, lint-checks 를 추가한다.
compile 시에만 참고할 수 있도록 compileOnly 로 추가해주었다.

dependencies {
	compileOnly "com.android.tools.lint:lint-api:$lint_version"
	compileOnly "com.android.tools.lint:lint-checks:$lint_version"
}

 

// build.gradle

apply plugin: 'java-library'
apply plugin: 'kotlin'
apply plugin: 'com.android.lint'

sourceSets {
    main.java.srcDirs += 'src/main/kotlin'
}

sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8

dependencies {
    def lint_version = '27.0.0'

    // kotlin
    compileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"

    // lint
    compileOnly "com.android.tools.lint:lint-api:$lint_version"
    compileOnly "com.android.tools.lint:lint-checks:$lint_version"

    // test
    testImplementation 'junit:junit:4.13'
    testImplementation "com.android.tools.lint:lint-tests:$lint_version"
}

 

2. IssueRegistry 구현

IssueRegistry 는 안드로이드 프로젝트에서 수행 할 검사 목록을 제공하는 레지스트리이다.
일단은 간단하게 구성만 갖춘 후 build.gradle 에 적용까지 해본다.

class LintIssueRegistry : IssueRegistry() {

    // lint api 버전 설정
    override val api = CURRENT_API

    // 검사를 수행 할 이슈 등록
    override val issues = listOf()

}


lint 검사를 수행할 수 있도록 build.gradle 에 IssueRegistry 를 등록한다.

dependencies { ... }

jar {
    manifest {
        attributes("Lint-Registry-v2": "com.sample.lint.LintIssueRegistry")
    }
}

 

3. Issue 생성

DataBinding 사용을 권장하며 setContentView() 사용 시 warning 을 띄워주는 목적의 이슈를 만들어본다.
Issue.create() 로 생성가능하며 아래 모든 내용을 필수로 채워주어야한다.

  • id : Issue 의 고유 식별 문자
  • briefDescription : Issue 요약 설명으로 길지 않게 작성한다
  • explanation : Issue 에 대한 구체적인 설명과 권장 사항을 작성한다
  • category : lint Ctegory 기준에 맞게 작성한다
  • priority : Issue 의 우선순위이며 1~10 사이의 정수를 적는다
  • severity : Issue 심각도 레벨을 지정한다
  • implementation : 해당 detector 를 구현하고 검사 수행 할 scope 도 설정해준다

(아래 나올 detector 에 companion object 에 포함할 예정이다)

val ISSUE = Issue.create(
    id = SetContentViewDetector::class.java.simpleName,
    briefDescription = "Prohibits usages of SetContentView()",
    explanation = "Prohibits usages of SetContentView(), use DataBindingUtil.setContentView() instead",
    category = Category.CORRECTNESS,
    priority = 5,
    severity = Severity.WARNING,
    implementation = Implementation(SetContentViewDetector::class.java, Scope.JAVA_FILE_SCOPE))

 

4. Detector 구현

  • 수행 할 검사의 이슈를 만드는 클래스로 기본적으로 Detector 를 상속받아 구현한다
  • 추가로 implement 할 인터페이스는 아래 2가지이다
    • XmlScanner : xml 파일을 검사
    • SourceCodeScanner : 소스코드 파일을 검사
  • setContentView 라는 메소드를 사용했을 경우 보고할 예정이라 getApplicableMethodNames() 를 override 하여 리스트에 setContentView 를 넣어준다
  • visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod)
    • 위 getApplicableMethodNames() 에서 반환된 메서드의 이름이 검출되면 호출된다
    • JavaContext : 분석한 소스코드 파일에 대한 정보
    • UCallExpression : 호출된 메소드의 노드 정보
    • PsiMethod : 호출된 메소드를 표현
class SetContentViewDetector : Detector(), SourceCodeScanner {

    companion object {
        val ISSUE = Issue.create(
            id = SetContentViewDetector::class.java.simpleName,
            briefDescription = "Prohibits usages of SetContentView()",
            explanation = "Prohibits usages of SetContentView(), use DataBindingUtil.setContentView() instead",
            category = Category.CORRECTNESS,
            priority = 5,
            severity = Severity.WARNING,
            implementation = Implementation(SetContentViewDetector::class.java, Scope.JAVA_FILE_SCOPE))
    }

    // 반환된 리스트의 메소드명을 소스코드에서 검출
    override fun getApplicableMethodNames(): List<String>? {
        return listOf("setContentView")
    }

    // getApplicableMethodNames() 에서 반환된 메서드의 이름이 검출되면 호출됨
    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
        if (context.evaluator.isMemberInClass(method, "androidx.databinding.DataBindingUtil")) {
            return
        }
        context.report(ISSUE, node, context.getLocation(node), "Use DataBindingUtil.setContentView() instead.")
    }

}

 

5. Issue 등록

class LintIssueRegistry : IssueRegistry() {

    // lint api 버전
    override val api = CURRENT_API

    // issue 등록
    override val issues = listOf(SetContentViewDetector.ISSUE)

}

 

6. lint module 사용하기

사용하고자하는 프로젝트에 lint module 을 추가한다.

// module 수준의 build.gradle
dependencies {
    lintChecks project(':lint')
}

// settings.gradle
include ':lint'


sync gradle 후 setContentView() 를 사용한 곳에 가면 아래와 같은 warning 을 확인할 수 있다.
Issue 생성 시 작성했던 briefDescription, explanation, id 도 확인할 수 있다.

 

참고)
Google Custom Lint : https://github.com/googlesamples/android-custom-lint-rules

'Android개발' 카테고리의 다른 글

Migrating build.gradle from Groovy to Kotlin  (0) 2020.09.25
Android Lint #1 - 기본  (0) 2020.07.02
App Startup time  (1) 2020.06.25
App Startup  (0) 2020.06.25
Elevation  (0) 2020.03.18

댓글