Merge "Fix NonNullableMutableLiveData lint check" into androidx-master-dev
diff --git a/lifecycle/lifecycle-livedata-core-ktx-lint/build.gradle b/lifecycle/lifecycle-livedata-core-ktx-lint/build.gradle
index 2a1a5f5..3a7be4e 100644
--- a/lifecycle/lifecycle-livedata-core-ktx-lint/build.gradle
+++ b/lifecycle/lifecycle-livedata-core-ktx-lint/build.gradle
@@ -37,6 +37,8 @@
}
compileOnly KOTLIN_STDLIB
+ compileOnly LINT_CORE
+
testImplementation KOTLIN_STDLIB
testImplementation LINT_CORE
testImplementation LINT_TESTS
diff --git a/lifecycle/lifecycle-livedata-core-ktx-lint/src/main/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetector.kt b/lifecycle/lifecycle-livedata-core-ktx-lint/src/main/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetector.kt
index 10a133f..2bb57da 100644
--- a/lifecycle/lifecycle-livedata-core-ktx-lint/src/main/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetector.kt
+++ b/lifecycle/lifecycle-livedata-core-ktx-lint/src/main/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetector.kt
@@ -16,6 +16,7 @@
package androidx.lifecycle.lint
+import com.android.tools.lint.checks.DataFlowAnalyzer
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
@@ -38,8 +39,10 @@
import org.jetbrains.uast.UClass
import org.jetbrains.uast.UElement
import org.jetbrains.uast.UReferenceExpression
+import org.jetbrains.uast.getParentOfType
import org.jetbrains.uast.getUastParentOfType
import org.jetbrains.uast.isNullLiteral
+import org.jetbrains.uast.kotlin.KotlinUField
import org.jetbrains.uast.kotlin.KotlinUSimpleReferenceExpression
import org.jetbrains.uast.resolveToUElement
@@ -77,16 +80,38 @@
if (!isKotlin(node.sourcePsi) || !context.evaluator.isMemberInSubClassOf(method,
"androidx.lifecycle.LiveData", false)) return
+ val fieldTypes = mutableListOf<KtTypeReference>()
+
+ val analyzer = node.getParentOfType<UClass>(UClass::class.java, true) ?: return
+ analyzer.accept(object : DataFlowAnalyzer(listOf(node)) {
+ override fun visitClass(node: UClass): Boolean {
+ for (element in node.uastDeclarations) {
+ if (element is KotlinUField) {
+ (element.sourcePsi?.children?.get(0) as? KtCallExpression)
+ ?.typeArguments?.singleOrNull()?.typeReference?.let {
+ fieldTypes.add(it)
+ }
+ }
+ }
+ return super.visitClass(node)
+ }
+ })
+
val receiverType = node.receiverType as PsiClassType
- val liveDataType = if (receiverType.hasParameters()) {
- val receiver = (node.receiver as? KotlinUSimpleReferenceExpression)?.resolve() ?: return
- val assignment = UastLintUtils.findLastAssignment(receiver as PsiVariable, node)
- ?: return
- val constructorExpression = assignment.sourcePsi as? KtCallExpression
- constructorExpression?.typeArguments?.singleOrNull()?.typeReference
+ val liveDataType = if (fieldTypes.isNullOrEmpty()) {
+ if (receiverType.hasParameters()) {
+ val receiver =
+ (node.receiver as? KotlinUSimpleReferenceExpression)?.resolve() ?: return
+ val assignment = UastLintUtils.findLastAssignment(receiver as PsiVariable, node)
+ ?: return
+ val constructorExpression = assignment.sourcePsi as? KtCallExpression
+ constructorExpression?.typeArguments?.singleOrNull()?.typeReference
+ } else {
+ getTypeArg(receiverType)
+ } ?: return
} else {
- getTypeArg(receiverType)
- } ?: return
+ fieldTypes[0]
+ }
if (liveDataType.typeElement !is KtNullableType) {
val fixes = mutableListOf<LintFix>()
diff --git a/lifecycle/lifecycle-livedata-core-ktx-lint/src/test/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetectorTest.kt b/lifecycle/lifecycle-livedata-core-ktx-lint/src/test/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetectorTest.kt
index 9094fd1..2f5cd03 100644
--- a/lifecycle/lifecycle-livedata-core-ktx-lint/src/test/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetectorTest.kt
+++ b/lifecycle/lifecycle-livedata-core-ktx-lint/src/test/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetectorTest.kt
@@ -128,6 +128,114 @@
}
@Test
+ fun nullLiteralFailField() {
+ check(
+ kotlin(
+ """
+ package com.example
+
+ import androidx.lifecycle.MutableLiveData
+
+ val liveDataField = MutableLiveData<Boolean>()
+
+ fun foo() {
+ liveDataField.value = null
+ }
+ """
+ ).indented()
+ ).expect("""
+src/com/example/test.kt:8: Error: Cannot set non-nullable LiveData value to null [NullSafeMutableLiveData]
+ liveDataField.value = null
+ ~~~~
+1 errors, 0 warnings
+ """)
+ }
+
+ @Test
+ fun nullLiteralFailMultipleFields() {
+ check(
+ kotlin(
+ """
+ package com.example
+
+ import androidx.lifecycle.MutableLiveData
+
+ val liveDataField = MutableLiveData<Boolean>()
+ val secondLiveDataField = MutableLiveData<String>()
+
+ fun foo() {
+ liveDataField.value = null
+ secondLiveDataField.value = null
+ }
+ """
+ ).indented()
+ ).expect("""
+src/com/example/test.kt:9: Error: Cannot set non-nullable LiveData value to null [NullSafeMutableLiveData]
+ liveDataField.value = null
+ ~~~~
+src/com/example/test.kt:10: Error: Cannot set non-nullable LiveData value to null [NullSafeMutableLiveData]
+ secondLiveDataField.value = null
+ ~~~~
+2 errors, 0 warnings
+ """)
+ }
+
+ @Test
+ fun nullLiteralFailFieldAndIgnore() {
+ check(
+ kotlin(
+ """
+ package com.example
+
+ import androidx.lifecycle.MutableLiveData
+
+ val liveDataField = MutableLiveData<Boolean>()
+ val ignoreThisField = ArrayList<String>(arrayListOf("a", "b"))
+
+ fun foo() {
+ liveDataField.value = null
+ ignoreThisField[0] = null
+ }
+ """
+ ).indented()
+ ).expect("""
+src/com/example/test.kt:9: Error: Cannot set non-nullable LiveData value to null [NullSafeMutableLiveData]
+ liveDataField.value = null
+ ~~~~
+1 errors, 0 warnings
+ """)
+ }
+
+ @Test
+ fun nullLiteralFailFieldAndLocalVariable() {
+ check(
+ kotlin(
+ """
+ package com.example
+
+ import androidx.lifecycle.MutableLiveData
+
+ val liveDataField = MutableLiveData<Boolean>()
+
+ fun foo() {
+ liveDataField.value = null
+ val liveDataVariable = MutableLiveData<Boolean>()
+ liveDataVariable.value = null
+ }
+ """
+ ).indented()
+ ).expect("""
+src/com/example/test.kt:8: Error: Cannot set non-nullable LiveData value to null [NullSafeMutableLiveData]
+ liveDataField.value = null
+ ~~~~
+src/com/example/test.kt:10: Error: Cannot set non-nullable LiveData value to null [NullSafeMutableLiveData]
+ liveDataVariable.value = null
+ ~~~~
+2 errors, 0 warnings
+ """)
+ }
+
+ @Test
fun nullLiteralQuickFix() {
check(
kotlin("""