diff --git a/app/build.gradle b/app/build.gradle index 27f339f..5278e52 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' + id 'kotlin-kapt' } android { @@ -30,6 +31,10 @@ android { kotlinOptions { jvmTarget = '17' } + + buildFeatures { + dataBinding = true + } } dependencies { diff --git a/app/src/main/java/com/pluu/lintstudy/databinding_duplication/SampleView.kt b/app/src/main/java/com/pluu/lintstudy/databinding_duplication/SampleView.kt new file mode 100644 index 0000000..e636ad8 --- /dev/null +++ b/app/src/main/java/com/pluu/lintstudy/databinding_duplication/SampleView.kt @@ -0,0 +1,27 @@ +package com.pluu.lintstudy.databinding_duplication + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import androidx.databinding.BindingAdapter + +class SampleView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyle: Int = 0 +) : View(context, attrs, defStyle) { + + companion object { + @JvmStatic + @BindingAdapter("android:paddingLeft") + fun setPaddingLeft(view: View, padding: Int) { + // TBD + } + + @BindingAdapter("android:paddingStart") + fun setPaddingStart(view: SampleView, padding: Int) { + // TBD + } + } +} + diff --git a/app/src/main/java/com/pluu/lintstudy/databinding_duplication/ViewBindingAdapter.kt b/app/src/main/java/com/pluu/lintstudy/databinding_duplication/ViewBindingAdapter.kt new file mode 100644 index 0000000..554a378 --- /dev/null +++ b/app/src/main/java/com/pluu/lintstudy/databinding_duplication/ViewBindingAdapter.kt @@ -0,0 +1,22 @@ +package com.pluu.lintstudy.databinding_duplication + +import android.view.View +import androidx.databinding.BindingAdapter + +object ViewBindingAdapter { + @JvmStatic + @BindingAdapter("test1") + fun setTest1(view: View, value: Int) { + // TBD + } + + @BindingAdapter("test2") + fun setTest2(view: View, value: Int) { + // TBD + } +} + +@BindingAdapter("test3") +fun setTest3(view: View, value: Int) { + // TBD +} \ No newline at end of file diff --git a/lint/src/main/java/com/pluu/lint/DataBindingDuplicationDetector.kt b/lint/src/main/java/com/pluu/lint/DataBindingDuplicationDetector.kt new file mode 100644 index 0000000..033db49 --- /dev/null +++ b/lint/src/main/java/com/pluu/lint/DataBindingDuplicationDetector.kt @@ -0,0 +1,69 @@ +package com.pluu.lint + +import com.android.tools.lint.client.api.UElementHandler +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.isKotlin +import org.jetbrains.kotlin.psi.KtObjectDeclaration +import org.jetbrains.uast.UClass +import java.util.EnumSet + +class DataBindingDuplicationDetector : Detector(), Detector.UastScanner { + + override fun getApplicableUastTypes() = listOf(UClass::class.java) + + override fun createUastHandler(context: JavaContext): UElementHandler? { + if (!isKotlin(context.psiFile)) return null + return object : UElementHandler() { + override fun visitClass(node: UClass) { + val element = node.sourceElement ?: return + if (element is KtObjectDeclaration && element.isCompanion()) { + node.methods + .filter { method -> + detectAnnotation.any { annotation -> + method.hasAnnotation(annotation) + } && method.hasAnnotation(jvmStaticAnnotation) + }.forEach { method -> + context.report( + ISSUE, + method, + context.getLocation(method), + message + ) + } + } + } + } + } + + + companion object { + private val detectAnnotation = arrayOf( + "androidx.databinding.BindingAdapter" + ) + + private val jvmStaticAnnotation = "kotlin.jvm.JvmStatic" + + private const val message = "복수 DataBinding 생성으로 IDE 경고가 발생합니다." + + @JvmField + val ISSUE = Issue.create( + id = DataBindingDuplicationDetector::class.java.simpleName, + briefDescription = "DataBindingDuplicationDetector", + explanation = message, + category = Category.CORRECTNESS, + priority = 6, + severity = Severity.WARNING, + implementation = Implementation( + DataBindingDuplicationDetector::class.java, + EnumSet.of(Scope.JAVA_FILE), + Scope.JAVA_FILE_SCOPE + ) + ) + } +} \ No newline at end of file diff --git a/lint/src/main/java/com/pluu/lint/MyIssueRegistry.kt b/lint/src/main/java/com/pluu/lint/MyIssueRegistry.kt index 0bfb936..5337daf 100644 --- a/lint/src/main/java/com/pluu/lint/MyIssueRegistry.kt +++ b/lint/src/main/java/com/pluu/lint/MyIssueRegistry.kt @@ -16,6 +16,7 @@ class MyIssueRegistry : IssueRegistry() { RequiredCustomViewAttributeDetector.ISSUE, TypoMethodInComponentDetector.ISSUE, LiveDataObserveNotNullDetector.ISSUE, - LazyBundleDetector.ISSUE + LazyBundleDetector.ISSUE, + DataBindingDuplicationDetector.ISSUE ) } \ No newline at end of file diff --git a/lint/src/test/java/com/pluu/lint/DataBindingDuplicationDetectorTest.kt b/lint/src/test/java/com/pluu/lint/DataBindingDuplicationDetectorTest.kt new file mode 100644 index 0000000..f301880 --- /dev/null +++ b/lint/src/test/java/com/pluu/lint/DataBindingDuplicationDetectorTest.kt @@ -0,0 +1,102 @@ +package com.pluu.lint + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.detector.api.Detector +import com.pluu.lint.stubs.DataBindingStub +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +class DataBindingDuplicationDetectorTest : LintDetectorTest() { + override fun getDetector(): Detector = DataBindingDuplicationDetector() + + override fun getIssues() = listOf(DataBindingDuplicationDetector.ISSUE) + + @Test + fun testDetectCase() { + lint() + .files( + DataBindingStub.BindingAdapterStub, + kotlin( + """ +import androidx.databinding.BindingAdapter + +class SampleView { + companion object { + @JvmStatic + @BindingAdapter("test") + fun setTest(view: View, value: Int) { + } + } +} + """ + ) + .indented(), + ) + .run() + .expect( + """ +src/SampleView.kt:5: Warning: 복수 DataBinding 생성으로 IDE 경고가 발생합니다. [DataBindingDuplicationDetector] + @JvmStatic + ^ +0 errors, 1 warnings + """.trimIndent() + ) + } + + @Test + fun testNonDetectCase_NonJvmStatic() { + lint() + .files( + DataBindingStub.BindingAdapterStub, + kotlin( + """ +import androidx.databinding.BindingAdapter + +class SampleView { + companion object { + @BindingAdapter("test") + fun setTest(view: View, value: Int) { + } + } +} + """ + ) + .indented(), + ) + .run() + .expectClean() + } + + @Test + fun testNonDetectCase_AnotherClass() { + lint() + .files( + DataBindingStub.BindingAdapterStub, + kotlin( + """ +import androidx.databinding.BindingAdapter + +object ViewBindingAdapter { + @JvmStatic + @BindingAdapter("test1") + fun setTest1(view: View, value: Int) { + } + + @BindingAdapter("test2") + fun setTest2(view: View, value: Int) { + } +} + +@BindingAdapter("test3") +fun setTest3(view: View, value: Int) { +} + """ + ) + .indented(), + ) + .run() + .expectClean() + } +} \ No newline at end of file diff --git a/lint/src/test/java/com/pluu/lint/stubs/DataBindingStub.kt b/lint/src/test/java/com/pluu/lint/stubs/DataBindingStub.kt new file mode 100644 index 0000000..717c53f --- /dev/null +++ b/lint/src/test/java/com/pluu/lint/stubs/DataBindingStub.kt @@ -0,0 +1,17 @@ +package com.pluu.lint.stubs + +import com.android.tools.lint.checks.infrastructure.TestFiles.java + +object DataBindingStub { + + val BindingAdapterStub = java( + """ +package androidx.databinding; +public @interface BindingAdapter { + String[] value(); + boolean requireAll() default true; +} + """.trimIndent() + ) + +} \ No newline at end of file