본문 바로가기

Android/Lint

[Lint] Lint에 Custom rule을 추가해보자. - 4. LintFix를 통한 Lint 수정

728x90

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

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

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

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

 

이번 게시글에서는 LintFix를 사용하여 지정된 format으로 Lint를 수정하는 방법에 대하여 작성하고자 한다.

 

기본적으로 Lint는 적용이 되어있고,

Window 기준 Alt + Enter

MacOs 기준 Option + Enter

를 입력하면 지정된 format으로 수정할 수 있는 QuickFix 선택지를 제공해 준다.

 

 

이처럼 말이다.

 

하지만, 지금까지 작성했던 Lint에 대해서는 수정에 대한 정보를 하나도 기입하지 않았으므로 위와 같은 부분은 나오지 않고,

 

 

이와 같이 해당 Lint를 무시하려면 @SuppressLint 어노테이션을 사용하라고 나온다.

 

따라서, Convention에 어긋나는 항목들을 Convention에 맞춰서 쉽게 수정할 수 있도록 LintFix를 추가하여

 

 

이와 같이 자동으로 수정할 수 있도록 작업을 해보고자 한다.


LintFix Class를 확인해보면, Lint 경고에 대한 빠른 수정을 도와주기 위하여 사용할 수 있다고 나와있으며, 점점 더 확장될 예정이라고 나와있다.

즉, LintFix Class를 사용하여 위에서 언급했던 수정을 구현할 수 있다는 것이다.

 

우선,

LintFix를 사용하는 방법에 대해서 알아보자.

 

지금까지 우리가 Lint 경고를 보여주기 위해서 사용했던 JavaContext.report()를 기억하고 있을 것이다.

여기서 report에 들어가는 매개변수를 확인해 보면 다음과 같이 나와있다.

 

 

위의 사진은, UMethod를 사용하는 경우의 overloading 된 report 메서드이다.

자세히 확인해보면 quickfixData: LintFix?라는 매개변수의 사용 여부에 따라 2개로 나누어져 있음을 알 수 있다.

 

즉, report 메서드를 사용할 때, 마지막 매개변수로 LintFix 객체를 사용하면 된다는 것이다.

 

그렇다면 LintFix 객체를 어떻게 생성해서 쓰는지 확인해보자.

이에 따른 정확한 가이드를 찾을 수 없어서, 구글링을 통해 사용하는 예제를 보고 사용 방법을 파악했다.

 

LintFix.create()
    .replace()
    .text("oldText")
    .with("newText")
    .build()

 

가장 기본적인 LintFIx의 사용 방법이다.

 

메서드 이름을 보면 어떻게 흘러가는지 알 수 있을 것이다.

 

  • LintFix.create() : LintFix 객체를 만들 건데,
  • .replace() : 값을 교체할 거야.
  • .text(oldText: String) : oldText를
  • .with(newText: String) : newText로 변경하는
  • .build() : LintFix를 만들어.

라는 흐름으로 생각하면 된다.

 

 

즉,

 

LintFix.create()
    ...
    .build()

 

라는 형태로 LintFix를 만들어서 report 할 때 사용하면 된다.

 

context.report(
    ISSUE,
    node as UElement,
    context.getNameLocation(node),
    "Maybe, This code style requires all caps. For example, $upperCase",
    LintFix.create()
        .replace()
        .text(name)
        .with(upperCase)
        .build()
)

 

필자는 다음과 같이 report를 만들어 보았고, 

그 결과

 

 

와 같은 quickfixData를 확인할 수 있었다.

여기서 name은 sample_test가 들어가고 upperCase는 smapleTest가 들어가 있게 된다.

 

기본적으로 LintFix를 사용할 때, 기존의 텍스트를 새로운 텍스트로 변경하는 형태로 많이 사용할 것으로 보인다.

따라서, 이 정도의 수정만 해주어도 Convention을 맞추는 것이 어느 정도 편해질 것으로 생각된다.

 

하지만, Class Name Convention의 경우는 조금 더 생각을 해보아야 한다.

Method나 Variable와 같은 경우 단순히 이름만 변경하면 되지만, Class의 경우 inner class가 아니라면 파일 명 자체를 변경해야 한다.

따라서, 위와 같은 방법을 사용해서 단순히 이름만 변경하면 안 되고 파일 명 자체를 바꾸거나, 아니면 변경 자체를 하지 않아야 한다.

 

LintFix 클래스에서 사용할 수 있는 메서드 중에 newFile이라는 메서드가 존재하지만, 필자가 정확히 사용하는 방법을 모르기 때문일지도 있겠지만 파일 명을 변경하는 용도로는 사용할 수가 없었다.

파일 명 자체를 변경할 수는 없으니 Lint를 무시할 수 있는 @SuppressLint 어노테이션을 추가하는 방식으로 Lint를 해결하도록 하였다.

 

fun fix(context: JavaContext, node: UClass): LintFix = LintFix.create()
    .replace()
    .text("")
    .with("@SuppressLint(\"Lint-ClassNameDetector\")\n")
    .beginning() // range의 시작 범위에 삽입
    .shortenNames()
    .reformat(true)
    .range(context.getLocation(node as UElement))
    .build()

 

해당 부분은 사용하는 메서드가 많아서 따로 함수로 빼내어 작성하였다.

 

with에 들어가는 @SuppressLint 어노테이션은, 해당 Issue의 ID가 들어가야 정상적으로 Lint를 무시할 수 있도록 어노테이션이 추가된다.

 

위의 LintFix에서 다른 부분과 동일하게,

replace, text와 with 그리고 build만 사용해서 작성해도 정상적으로 동작하기는 한다.

하지만, 예제를 여러 개 찾아본 결과 위와 같이 사용한 부분이 많았다.

 

Class에 대한 lint를 작성할 때는 이름이 아니라 특정 메서드를 반드시 포함하도록 한다던지 등, 상당히 많은 양의 코드를 삽입해야 하는 경우로 사용이 되는 것 같다.

그러한 경우에는 위에 추가한 메서드를 사용하여 작성해야 정상적으로 동작한다고 한다.

 

해당 메서드들에 달린 주석을 확인해보아도 어떠한 상황에 사용한다는 등의 정보는 알 수 없어서 왜 사용하는지에 대해서 정확히 판단하지는 못했지만, 추후에 어떠한 문제가 생겼을 때 확인을 하기 위해 그대로 남겨두도록 하였다.

해당 부분에 대해 추가적으로 알게 된 정보가 있다면 수정하도록 하겠다.

 

 

결과적으로, 이런 식으로 나오게 되고 빠른 수정을 수행하게 되면

 

 

생각한 대로 @SuppressLint 어노테이션이 추가되어 Lint Error가 뜨지 않게 된다.


 

Class Name에 대한 Lint를 추가할 때,

Method Name에 대한 동일한 Lint Rule을 추가한다면 Class에서 2가지의 Lint가 나오게 된다.

 

따라서, 위의 @SuppressLint를 통해 Lint를 무시하도록 어노테이션을 추가하여도 Method에 대한 Lint Rule 때문에 Lint 표시가 사라지지 않는다.

 

해당 문제를 해결하기 위하여 이전에 작성해둔 Method Name에 대한 Report를 할 때 조건을 추가해 주었다.

 

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

 

node.returnType는 Method에서 return Type을 반환해주는 메서드이다.

return Type이 없는 경우에는 Void를 반환해 주는데, 여기서 Class의 경우 null을 반환해 준다.

 

따라서, 해당 값을 사용해서 반환되는 값이 null이 아닐 경우에만 Method Name에 대한 Lint를 발생시키도록 한다면,

Class Name에 대한 Lint에서 해당 Lint가 발생하지 않게 된다.


LintFix를 사용하여 빠른 수정을 할 수 있도록 몇 가지 추가해보았다.

생각보다 LintFix에 대한 정보가 많지 않아서 메서드가 어떠한 역할을 하는지, 어떻게 사용해야 하는지에 대해 알기가 쉽지 않았다.

하지만, 기본적으로 사용할 수 있는 방식을 통해서 우리가 원하는 Code Convention은 맞출 수 있는 것으로 보여 다행이라고 생각되었다.

 

물론, Convention에 맞춰서 수정하기 위해서는 케이스에 따라 어떻게 수정이 되어야 하는지 모두 작성하여 적용해야 하지만, 한번 잘 적용해두면 계속해서 사용할 수 있으니 자주 틀리는 Convention의 경우 적용해 두는 것도 나쁘지 않아 보인다.

 

예제에서 확인했던 Class Convention Lint에 대해서는 좀 더 많은 스터디가 필요할 것으로 보인다.

하지만, 아직까지 그런 식으로 Convention을 맞춰야 하는 경우를 경험하지 못했기 때문에 함수를 추가한다던지 등 범위가 큰 작업에 대해서는 필요하다면 그때 공부하여 적용해볼 예정이다.

 

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

https://github.com/HeeGyeong/CleanArchitectureSample

 

GitHub - HeeGyeong/CleanArchitectureSample

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

github.com

728x90