Disable Java-side support for ReplaceWith on Kotlin properties

We don't currently map property expressions to/from their corresponding
accessor methods, so we should avoid showing replacements for properties.

Note that we're still showing replacements on physical functions where the
replacement expression is a property -- that's hard to ignore, since we'd
need to parse the expression to know that it's a property.

Bug: 323214452
Test: ReplaceWithDetectorTest
Change-Id: I5dd76d71969199535da5d38daad622a082b0ee69
diff --git a/lint-checks/integration-tests/src/main/java/replacewith/PropertyJava.java b/lint-checks/integration-tests/src/main/java/replacewith/PropertyJava.java
new file mode 100644
index 0000000..673222a
--- /dev/null
+++ b/lint-checks/integration-tests/src/main/java/replacewith/PropertyJava.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package replacewith;
+
+/**
+ * Usage of a deprecated Kotlin property.
+ */
+@SuppressWarnings({"deprecation", "unused"})
+public class PropertyJava {
+    void propertyGetter() {
+        ReplaceWithUsageKotlin clazz = new ReplaceWithUsageKotlin();
+        clazz.getSomeProperty();
+        clazz.getSomeBooleanProperty();
+        clazz.getDeprecatedSetGetProperty();
+        clazz.getDeprecatedAccessorProperty();
+    }
+
+    void propertySetter() {
+        ReplaceWithUsageKotlin clazz = new ReplaceWithUsageKotlin();
+        clazz.setSomeProperty("value");
+        clazz.setSomeBooleanProperty(false);
+        clazz.setDeprecatedSetGetProperty("value");
+        clazz.setDeprecatedAccessorProperty("value");
+    }
+
+    void methodToProperty() {
+        ReplaceWithUsageKotlin clazz = new ReplaceWithUsageKotlin();
+        clazz.setMethodDeprecated("value");
+        clazz.getMethodDeprecated();
+    }
+}
diff --git a/lint-checks/integration-tests/src/main/java/replacewith/ReplaceWithUsageKotlin.kt b/lint-checks/integration-tests/src/main/java/replacewith/ReplaceWithUsageKotlin.kt
index 8e3f9e7..04f0415 100644
--- a/lint-checks/integration-tests/src/main/java/replacewith/ReplaceWithUsageKotlin.kt
+++ b/lint-checks/integration-tests/src/main/java/replacewith/ReplaceWithUsageKotlin.kt
@@ -14,13 +14,73 @@
  * limitations under the License.
  */
 
-@file:Suppress("unused", "UNUSED_PARAMETER")
+@file:Suppress("unused", "UNUSED_PARAMETER", "MemberVisibilityCanBePrivate")
 
 package replacewith
 
 import android.view.View
 
 class ReplaceWithUsageKotlin {
+    var otherBooleanProperty: Boolean = false
+    var otherProperty: String = "value"
+
+    @Deprecated(
+        message = "Use [otherProperty] instead",
+        replaceWith = ReplaceWith("otherProperty")
+    )
+    var someProperty: String = "value"
+
+    @Deprecated(
+        message = "Use [otherBooleanProperty] instead",
+        replaceWith = ReplaceWith("otherBooleanProperty")
+    )
+    var someBooleanProperty: Boolean = false
+
+    @get:Deprecated(
+        message = "Use [getMethod] instead",
+        replaceWith = ReplaceWith("getMethod")
+    )
+    @set:Deprecated(
+        message = "Use [setMethod(String)] instead",
+        replaceWith = ReplaceWith("setMethod(value)")
+    )
+    var deprecatedSetGetProperty: String = "value"
+
+    var deprecatedAccessorProperty: String
+        @Deprecated(
+            message = "Use [getMethod] instead",
+            replaceWith = ReplaceWith("getMethod")
+        )
+        get() { return otherProperty }
+        @Deprecated(
+            message = "Use [setMethod(String)] instead",
+            replaceWith = ReplaceWith("setMethod(value)")
+        )
+        set(value) { otherProperty = value }
+
+    @Deprecated(
+        message = "Use [otherProperty] instead",
+        replaceWith = ReplaceWith("otherProperty = arg")
+    )
+    fun setMethodDeprecated(arg: String) {
+        otherProperty = arg
+    }
+
+    @Deprecated(
+        message = "Use [otherProperty] instead",
+        replaceWith = ReplaceWith("otherProperty")
+    )
+    fun getMethodDeprecated(): String {
+        return otherProperty
+    }
+
+    fun setMethod(arg: String) {
+        otherProperty = arg
+    }
+
+    fun getMethod(): String {
+        return otherProperty
+    }
 
     /**
      * Constructor.
diff --git a/lint-checks/integration-tests/src/main/java/replacewith/StaticKotlinMethodExplicitClass.java b/lint-checks/integration-tests/src/main/java/replacewith/StaticKotlinMethodExplicitClassJava.java
similarity index 94%
rename from lint-checks/integration-tests/src/main/java/replacewith/StaticKotlinMethodExplicitClass.java
rename to lint-checks/integration-tests/src/main/java/replacewith/StaticKotlinMethodExplicitClassJava.java
index 9009d1f..c36d8fe 100644
--- a/lint-checks/integration-tests/src/main/java/replacewith/StaticKotlinMethodExplicitClass.java
+++ b/lint-checks/integration-tests/src/main/java/replacewith/StaticKotlinMethodExplicitClassJava.java
@@ -20,7 +20,7 @@
  * Usage of a static method with an explicit class.
  */
 @SuppressWarnings({"deprecation", "unused"})
-class StaticKotlinMethodExplicitClass {
+class StaticKotlinMethodExplicitClassJava {
     void main() {
         ReplaceWithUsageKotlin.toString(this);
     }
diff --git a/lint-checks/integration-tests/src/main/java/replacewith/StaticKotlinMethodExplicitClass.java b/lint-checks/integration-tests/src/main/java/replacewith/StaticKotlinMethodExplicitClassKotlin.kt
similarity index 76%
copy from lint-checks/integration-tests/src/main/java/replacewith/StaticKotlinMethodExplicitClass.java
copy to lint-checks/integration-tests/src/main/java/replacewith/StaticKotlinMethodExplicitClassKotlin.kt
index 9009d1f..e2bbe52 100644
--- a/lint-checks/integration-tests/src/main/java/replacewith/StaticKotlinMethodExplicitClass.java
+++ b/lint-checks/integration-tests/src/main/java/replacewith/StaticKotlinMethodExplicitClassKotlin.kt
@@ -13,15 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package replacewith
 
-package replacewith;
+import replacewith.ReplaceWithUsageKotlin.Companion.toString
 
 /**
  * Usage of a static method with an explicit class.
  */
-@SuppressWarnings({"deprecation", "unused"})
-class StaticKotlinMethodExplicitClass {
-    void main() {
-        ReplaceWithUsageKotlin.toString(this);
+@Suppress("deprecation", "unused")
+internal class StaticKotlinMethodExplicitClassKotlin {
+    fun main() {
+        toString(this)
     }
 }
diff --git a/lint-checks/src/main/java/androidx/build/lint/ReplaceWithDetector.kt b/lint-checks/src/main/java/androidx/build/lint/ReplaceWithDetector.kt
index 9987d06..3c5bdd1 100644
--- a/lint-checks/src/main/java/androidx/build/lint/ReplaceWithDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/ReplaceWithDetector.kt
@@ -38,9 +38,11 @@
 import com.intellij.psi.PsiMethod
 import com.intellij.psi.PsiNewExpression
 import com.intellij.psi.impl.source.tree.TreeElement
+import org.jetbrains.kotlin.asJava.elements.KtLightMethod
 import org.jetbrains.kotlin.lexer.KtTokens
 import org.jetbrains.kotlin.psi.KtFile
 import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry
+import org.jetbrains.kotlin.psi.KtProperty
 import org.jetbrains.kotlin.psi.psiUtil.endOffset
 import org.jetbrains.uast.UAnnotation
 import org.jetbrains.uast.UCallExpression
@@ -74,6 +76,12 @@
         // Ignore callbacks for assignment on the original declaration of an annotated field.
         if (type == AnnotationUsageType.ASSIGNMENT_RHS && usage.uastParent == referenced) return
 
+        // [b/323214452] Don't replace property usages since we don't handle property accessors.
+        if ((referenced as? KtLightMethod)?.kotlinOrigin is KtProperty) return
+
+        // Don't warn for Kotlin replacement in Kotlin files -- that's the Kotlin Compiler's job.
+        if (qualifiedName == KOTLIN_DEPRECATED_ANNOTATION && isKotlin(usage.lang)) return
+
         var (expression, imports) = when (qualifiedName) {
             KOTLIN_DEPRECATED_ANNOTATION -> {
                 val replaceWith = annotation.findAttributeValue("replaceWith")?.unwrap()
diff --git a/lint-checks/src/test/java/androidx/build/lint/replacewith/ReplaceWithDetectorKotlinMethodTest.kt b/lint-checks/src/test/java/androidx/build/lint/replacewith/ReplaceWithDetectorKotlinMethodTest.kt
index f4ba47f..078eb2f 100644
--- a/lint-checks/src/test/java/androidx/build/lint/replacewith/ReplaceWithDetectorKotlinMethodTest.kt
+++ b/lint-checks/src/test/java/androidx/build/lint/replacewith/ReplaceWithDetectorKotlinMethodTest.kt
@@ -27,19 +27,19 @@
     fun staticMethodExplicitClass() {
         val input = arrayOf(
             ktSample("replacewith.ReplaceWithUsageKotlin"),
-            javaSample("replacewith.StaticKotlinMethodExplicitClass")
+            javaSample("replacewith.StaticKotlinMethodExplicitClassJava")
         )
 
         /* ktlint-disable max-line-length */
         val expected = """
-src/replacewith/StaticKotlinMethodExplicitClass.java:25: Information: Replacement available [ReplaceWith]
+src/replacewith/StaticKotlinMethodExplicitClassJava.java:25: Information: Replacement available [ReplaceWith]
         ReplaceWithUsageKotlin.toString(this);
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 0 errors, 0 warnings
         """.trimIndent()
 
         val expectedFixDiffs = """
-Fix for src/replacewith/StaticKotlinMethodExplicitClass.java line 25: Replace with `this.toString()`:
+Fix for src/replacewith/StaticKotlinMethodExplicitClassJava.java line 25: Replace with `this.toString()`:
 @@ -25 +25
 -         ReplaceWithUsageKotlin.toString(this);
 +         this.toString();
@@ -48,4 +48,20 @@
 
         check(*input).expect(expected).expectFixDiffs(expectedFixDiffs)
     }
+
+    @Test
+    fun staticMethodExplicitClass_withKotlinSource_hasNoWarnings() {
+        val input = arrayOf(
+            ktSample("replacewith.ReplaceWithUsageKotlin"),
+            ktSample("replacewith.StaticKotlinMethodExplicitClassKotlin")
+        )
+
+        /* ktlint-disable max-line-length */
+        val expected = """
+No warnings.
+        """.trimIndent()
+        /* ktlint-enable max-line-length */
+
+        check(*input).expect(expected)
+    }
 }
diff --git a/lint-checks/src/test/java/androidx/build/lint/replacewith/ReplaceWithDetectorPropertyTest.kt b/lint-checks/src/test/java/androidx/build/lint/replacewith/ReplaceWithDetectorPropertyTest.kt
new file mode 100644
index 0000000..7d032e1
--- /dev/null
+++ b/lint-checks/src/test/java/androidx/build/lint/replacewith/ReplaceWithDetectorPropertyTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.build.lint.replacewith
+
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ReplaceWithDetectorPropertyTest {
+
+    @Test
+    fun propertyUsage_isIgnored() {
+        val input = arrayOf(
+            ktSample("replacewith.ReplaceWithUsageKotlin"),
+            javaSample("replacewith.PropertyJava")
+        )
+
+        /* ktlint-disable max-line-length */
+        // TODO(b/323214452): This is incomplete, but we have explicitly suppressed replacement of
+        // Kotlin property accessors until we can properly convert the expressions to Java.
+        val expected = """
+src/replacewith/PropertyJava.java:42: Information: Replacement available [ReplaceWith]
+        clazz.setMethodDeprecated("value");
+              ~~~~~~~~~~~~~~~~~~~
+src/replacewith/PropertyJava.java:43: Information: Replacement available [ReplaceWith]
+        clazz.getMethodDeprecated();
+              ~~~~~~~~~~~~~~~~~~~
+0 errors, 0 warnings
+        """.trimIndent()
+
+        // TODO(b/323214452): These are incorrect, but we can't fix them unless we parse the
+        // expression as a property reference and (a) convert to Java or (b) ignore them.
+        val expectedFixDiffs = """
+Fix for src/replacewith/PropertyJava.java line 42: Replace with `otherProperty = "value"`:
+@@ -42 +42
+-         clazz.setMethodDeprecated("value");
++         clazz.otherProperty = "value"("value");
+Fix for src/replacewith/PropertyJava.java line 43: Replace with `otherProperty`:
+@@ -43 +43
+-         clazz.getMethodDeprecated();
++         clazz.otherProperty();
+        """.trimIndent()
+        /* ktlint-enable max-line-length */
+
+        check(*input).expect(expected).expectFixDiffs(expectedFixDiffs)
+    }
+}