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)
+ }
+}