본문 바로가기

Android/Lint

[Lint] Lint에 Custom rule을 추가해보자. - 3. Code Convention Rule

728x90

본 게시글은 이전 게시글에 이어서 작성된 부분입니다.

2022.04.13 - [Android/Lint] - [Lint] Lint에 Custom rule을 추가해보자. -1. 기본 설정 및 적용

2022.04.15 - [Android/Lint] - [Lint] Lint에 Custom rule을 추가해보자. -2. XML Rule

 

이번 게시글에서는 Custom Code Convention에 대하여 Lint를 적용할 수 있도록 도와주는 몇 가지의 Lint 조건을 추가해보고자 한다.

 

Class Name, Method Name, Variable name, value에 대한 Custom Lint Rule을 추가해 볼 예정이며,

Issue에 대한 내용은 첫 번째 기본 설정 및 적용에 대한 글에 작성되어 있으므로 생략하도록 하겠다.

 

글의 순서는 필자가 Lint Rule을 적용한 순서대로 글을 작성하였다.


 

우선,

작성해 볼 Class, Method, Variable 3가지에 대한 Rule을 추가할 때 Override 하는 하는 메서드는 동일하고 내부에 선언되는 부분에만 차이가 있다는 점을 먼저 언급하고 넘어가도록 하겠다.

 

필자가 가장 처음 적용한 린트는 Method Name에 대한 내용이다.

첫 번째 글에서 setContentView가 사용되는 것에 대한 Lint를 추가했었는데 해당 부분을 어느정도 커스텀하면 되지 않을까 라는 생각으로 접근을 했었다.

 

override fun getApplicableMethodNames(): List<String> {
    return listOf("setContentView")
}

 

해당 린트에서는 위의 메서드를 통하여 감지하고자 하는 메서드를 list로 작성하여 사용하는데,

Convention을 적용하기 위해서는 특정 메서드 뿐 아니라 모든 메서드에 대하여 감지할 수 있도록 조건을 걸어주어야 한다.

 

따라서 list에 전체를 포함할 수 있는 방법을 찾아보기 위하여 wildCard도 찾아보고 여러 가지를 확인해 봤는데,

해당 부분에서 List<String> 타입으로 모든 메서드를 감지할 수 있는 경우를 찾지 못하였다.

 

따라서, 해당 메서드가 아닌 다른 메서드를 사용하는 방법으로 구현하게 되었다.

 

override fun getApplicableUastTypes(): List<Class<out UElement?>> {
    // 모든 Method를 감지한다.
    return listOf(UMethod::class.java)
}

override fun createUastHandler(context: JavaContext): UElementHandler {
    return object : UElementHandler() {
        override fun visitMethod(node: UMethod) {
            val methodName = node.name
            // 찾고자 하는 Method Name
            if (methodName.matches(Regex(".*_.*"))) {
                context.report(
                    ISSUE,
                    node,
                    context.getLocation(node),
                    EXPLANATION
                )
            }
        }
    }
}

 

getApplicableUastTypes() 메서드를 사용하여 감지하고, UMethod class를 List로 넣음으로써 모든 Method를 검사하게 된다.

 

 

UMethod Class를 확인해 보면, 주석에 위와 같이 UastVisitor에서 사용할 메소드 방문자라고 나온다.

확인해보니, U~라는 형식으로 인터페이스가 여러 개 존재하고, 타입에 맞춰서 그것들을 사용하면 될 것으로 보인다.

 

여기서 사용되는 Uast는 Universal AST의 약자로, AST에 액세스 할 수 있도록 하는 추상 구문 트리 라이브러리다.

즉, Uast API를 사용하여 모든 코드를 검사하는데, 그 중 Method들을 감지하게 되는 것이다.

 

그 후, 이전에는 감지가 되면 visit~ 라는 메서드가 호출되어 그곳에서 별도의 처리를 진행하였는데, UastType을 통해 감지를 한 경우 visitUastTypes 같은 메서드가 존재하지 않고 UastHandler를 만들어서 처리를 해주어야 한다.

 

override fun createUastHandler(context: JavaContext): UElementHandler {
    return object : UElementHandler() {
        override fun visitMethod(node: UMethod) {
            
                    ...
                )
            }
        }
    }

 

사용한 형태를 보면 알 수 있듯이, visit~ 라는 메서드는 UElementHandler() 내부에 override 하여서 사용한다.

createUastHandler를 상위에 사용하기는 하지만 결과적으로 visit~ 형태의 메서드를 선언해서 처리하게 되는 것이다.

 

여기서 사용되는 UElementHandler를 확인해 보면,

 

 

단일 요소를 방문할 때 사용되는 것으로 주석에 설명이 작성되어 있다.

getApplicableUastTypes()를 통해 큰 범위의 감지할 리스트를 작성하고, UElementHandler를 override 하여 리스트 중 감지된 항목을 디테일하게 확인하게 되는 것이다.

 

override fun visitMethod(node: UMethod) {
    val methodName = node.name
    // 찾고자 하는 Method Name
    if (methodName.matches(Regex(".*_.*"))) {
        context.report(
            ISSUE,
            node,
            context.getLocation(node),
            EXPLANATION
        )
    }
}

 

node.name을 사용하여 감지된 Method의 이름을 저장하여 사용하는데, UMethod를 기준으로 감지하기 때문에 해당 부분에 모든 메서드가 들어오게 된다.

 

따라서, 우리가 필요로 하는 Convention에 맞춘 조건문을 작성하여 Convention에 어긋나는 경우 Lint를 보여주도록 구현하면 되는 것이다.

 

필자의 경우, 함수의 이름에 언더바(_)가 들어가는 경우 Lint를 발생시키도록 작성해두었다.

 

 

*

2022.04.21 추가.

위의 코드로 진행하게 되면, Method 뿐 아니라 Class도 감지하게 된다.

따라서, Class인 경우에 적용하고 싶지 않다면 node.returnType 을 체크하여 null이 아닌 경우에만 적용하도록 조건을 추가해 주면 된다.

Class의 경우 Return type이 없기 때문에 해당 값을 가져오면 null 값이 나오기 때문이다.

 

즉,

 

if (methodName.matches(Regex(".*_.*")) && node.returnType != null)

 

이렇게 작성하면 되는 것이다.


다음으로,

Class와 Variable Name, String 변수에 대한 Lint를 추가해보자.

상세 코드는 본 게시글 최 하단에 Github 링크를 작성하긴 할 거지만, Method의 경우와 완전히 동일하기 때문에 생략하도록 하겠다.

 

앞서 말했다시피 override 하여 사용하는 메서드는 동일하고, 내부 구현만 조금씩 다르게 사용하면 된다.

 

override fun getApplicableUastTypes(): List<Class<out UElement?>> {
    // 모든 Class를 감지한다.
    return listOf(UClass::class.java)
}

 

Method가 아닌 Class를 감지해야 하므로, UClass를 List에 넣어주었다.

이 부분도 UMethod 부분을 설명할 때 언급했듯이, U~라는 형식으로 인터페이스가 여러 개 존재하므로 찾아서 사용하였다.

 

override fun createUastHandler(context: JavaContext): UElementHandler {
    return object : UElementHandler() {
        override fun visitClass(node: UClass) {
            ...
        }
    }
}

 

createUastHandler 부분도, visitMethod가 아닌 visitClass를 사용하여 작성하여

node.name을 통해 Class 이름을 가져오고 원하는 조건에 맞춰서 Lint를 발생시켜준다.

 

이쯤 되면,

Variable의 경우도 어떻게 사용해야 하는지 알 수 있을 것이다.

 

override fun getApplicableUastTypes(): List<Class<out UElement?>> {
    // 모든 변수를 감지한다.
    return listOf(UVariable::class.java)
}

 

UVariable를 통해 모든 변수를 감지하고,

 

override fun createUastHandler(context: JavaContext): UElementHandler {
    return object : UElementHandler() {
        override fun visitVariable(node: UVariable) {
            ...
        }
    }
}

 

visitVariable을 통해 원하는 변수명을 가져와서 Convention에 따른 Lint를 발생시켜주면 된다.

 

마지막으로,

변수 값에 대한 Lint도 설정을 해보도록 하자.

 

override fun getApplicableUastTypes(): List<Class<out UElement?>> {
    return listOf(ULiteralExpression::class.java)
}

 

모든 변수에 할당되는 Value를 확인하기 위하여 ULiteralExpression 클래스를 List에 넣어주도록 한다.

해당 클래스를 통해, 모든 변수에 할당하는 값들을 확인할 수 있다.

 

override fun createUastHandler(context: JavaContext): UElementHandler {
    return object : UElementHandler() {
        override fun visitLiteralExpression(node: ULiteralExpression) {
            ...
        }
    }
}

 

동일하게 visit Method를 override 해서 사용하면 된다.

지금까지 위의 3개의 경우, node.name을 통해 이름을 가져온 후에 Convention에 맞춰서 Lint 조건을 걸어주면 되는데,

ULiteralExpression의 경우 제공하는 함수들이 많아서 필요에 따라 사용하면 될 것으로 보인다.

 

val value = node.value
val returnString = node.evaluateString()
val returnValue = node.evaluate()
val isBoolean = node.isBoolean
val isNull = node.isNull
val isString = node.isString

 

기본으로 node.value를 통해 값을 가져올 수 있는데,

특정 자료형에 따라서 Lint 적용이 필요한 경우 그 외 제공 함수를 사용하면 된다.

 

여기서 evaluateString을 사용하는 경우 node의 값이 String이 아닌 경우 null을 return 하므로 사용 시 null 체크를 해주어야 한다.


Custom Code Convention에 대한 Lint를 적용하려고 하니 생각보다 관련하여 제공되는 함수들이 많은 것으로 보인다.

 

변수에 대한 값에 대한 Convention 까지는 필요하지 않을 것이지만, String 타입의 변수를 사용할 때는 convention이 필요할 가능성도 있지 않을까?라는 생각으로 찾아서 추가해 보았다.

 

필자는 해당 조건을 추가하면서 사용 가능성이 있어 보이는 몇 가지를 조건으로 추가해서 테스트를 해보았는데,

각 회사에서 사용하는 별도의 Code Convention이 있는 경우에는 해당 조건에 맞춰서 감지하는 조건만 잘 작성해주면 편하게 convention을 맞출 수 있도록 도움을 받을 수 있을 것으로 보인다.

 

본 게시글을 작성하다가, Alt+Enter를 눌렀을 때 자동으로 해당 부분을 변경할 수 있는 LintFix라는 것을 발견하였다.

다음 게시글에서는 간단하게 해당 부분을 사용하는 방법에 대하여 작성해 볼 예정이다.

 

해당 게시글에 사용한 예제는 다음 Github에 올려두었다.

https://github.com/HeeGyeong/CleanArchitectureSample

 

GitHub - HeeGyeong/CleanArchitectureSample

Contribute to HeeGyeong/CleanArchitectureSample development by creating an account on GitHub.

github.com

 

728x90