Add support for inserting imports, fix string literal args

This doesn't make a lot of sense for Java, which can use fully-qualified
replacement expressions, but it's supported by Kotlin's annotation and
we'll need it for applying Kotlin fixes in Java source code.

Also uses toSourceString() rather than sourcePsi.text to ensure that we
correctly handle string literal arguments and include quotation marks.

Fixes: 322978342
Test: ReplaceWithDetectorMethodTest
Change-Id: Iff30433730bda4ed02f29ea55435a0b3c834e64c
diff --git a/lint-checks/integration-tests/src/main/java/replacewith/MethodWithImportsJava.java b/lint-checks/integration-tests/src/main/java/replacewith/MethodWithImportsJava.java
new file mode 100644
index 0000000..973ad52
--- /dev/null
+++ b/lint-checks/integration-tests/src/main/java/replacewith/MethodWithImportsJava.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+import androidx.annotation.ReplaceWith;
+
+/**
+ * Usage with implicit "this" receiver.
+ */
+@SuppressWarnings({"deprecation", "unused"})
+class MethodWithImportsJava {
+    @Deprecated
+    @ReplaceWith(expression = "newMethod(obj)", imports = "androidx.annotation.Deprecated")
+    void oldMethodSingleImport(Object obj) {}
+
+    @Deprecated
+    @ReplaceWith(expression = "newMethod(obj)", imports = {"androidx.annotation.Deprecated",
+            "androidx.annotation.NonNull"})
+    void oldMethodMultiImport(Object obj) {}
+
+    void newMethod(Object obj) {}
+
+    void usageSingleImport() {
+        oldMethodSingleImport(null);
+    }
+
+    void usageMultiImport() {
+        oldMethodMultiImport(null);
+    }
+}
diff --git a/lint-checks/integration-tests/src/main/java/replacewith/MethodWithImportsKotlin.kt b/lint-checks/integration-tests/src/main/java/replacewith/MethodWithImportsKotlin.kt
new file mode 100644
index 0000000..a8dccc3
--- /dev/null
+++ b/lint-checks/integration-tests/src/main/java/replacewith/MethodWithImportsKotlin.kt
@@ -0,0 +1,34 @@
+/*
+ * 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
+
+import androidx.annotation.AnyThread
+
+/**
+ * Usage of replacement with imports.
+ */
+@Suppress("deprecation", "unused")
+internal class MethodWithImportsKotlin {
+    @AnyThread
+    fun usageSingleImport() {
+        ReplaceWithUsageJava.toStringWithImport("hello")
+    }
+
+    @AnyThread
+    fun usageMultiImport() {
+        ReplaceWithUsageJava.toStringWithImports("world")
+    }
+}
diff --git a/lint-checks/integration-tests/src/main/java/replacewith/MethodWithNoImportsJava.java b/lint-checks/integration-tests/src/main/java/replacewith/MethodWithNoImportsJava.java
new file mode 100644
index 0000000..fbba87e
--- /dev/null
+++ b/lint-checks/integration-tests/src/main/java/replacewith/MethodWithNoImportsJava.java
@@ -0,0 +1,43 @@
+/*
+ * 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 with implicit "this" receiver.
+ */
+@SuppressWarnings({"deprecation", "unused"})
+class MethodWithNoImportsJava {
+    @Deprecated
+    @androidx.annotation.ReplaceWith(expression = "newMethod(obj)",
+            imports = "androidx.annotation.Deprecated")
+    void oldMethodSingleImport(Object obj) {}
+
+    @Deprecated
+    @androidx.annotation.ReplaceWith(expression = "newMethod(obj)",
+            imports = {"androidx.annotation.Deprecated", "androidx.annotation.NonNull"})
+    void oldMethodMultiImport(Object obj) {}
+
+    void newMethod(Object obj) {}
+
+    void usageSingleImport() {
+        oldMethodSingleImport(null);
+    }
+
+    void usageMultiImport() {
+        oldMethodMultiImport(null);
+    }
+}
diff --git a/lint-checks/integration-tests/src/main/java/replacewith/MethodWithNoImportsKotlin.kt b/lint-checks/integration-tests/src/main/java/replacewith/MethodWithNoImportsKotlin.kt
new file mode 100644
index 0000000..5063755
--- /dev/null
+++ b/lint-checks/integration-tests/src/main/java/replacewith/MethodWithNoImportsKotlin.kt
@@ -0,0 +1,30 @@
+/*
+ * 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 replacement with imports.
+ */
+@Suppress("deprecation", "unused")
+internal class MethodWithNoImportsKotlin {
+    fun usageSingleImport() {
+        ReplaceWithUsageJava.toStringWithImport("hello")
+    }
+
+    fun usageMultiImport() {
+        ReplaceWithUsageJava.toStringWithImports("world")
+    }
+}
diff --git a/lint-checks/integration-tests/src/main/java/replacewith/MethodWithNoImportsOrPackage.java b/lint-checks/integration-tests/src/main/java/replacewith/MethodWithNoImportsOrPackage.java
new file mode 100644
index 0000000..5be7377
--- /dev/null
+++ b/lint-checks/integration-tests/src/main/java/replacewith/MethodWithNoImportsOrPackage.java
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+/**
+ * Usage with implicit "this" receiver.
+ */
+@SuppressWarnings({"deprecation", "unused", "WrongPackageStatement", "DefaultPackage"})
+class MethodWithNoImportsOrPackage {
+    @Deprecated
+    @androidx.annotation.ReplaceWith(expression = "newMethod(obj)",
+            imports = "androidx.annotation.Deprecated")
+    void oldMethodSingleImport(Object obj) {}
+
+    @Deprecated
+    @androidx.annotation.ReplaceWith(expression = "newMethod(obj)",
+            imports = {"androidx.annotation.Deprecated", "androidx.annotation.NonNull"})
+    void oldMethodMultiImport(Object obj) {}
+
+    void newMethod(Object obj) {}
+
+    void usageSingleImport() {
+        oldMethodSingleImport(null);
+    }
+
+    void usageMultiImport() {
+        oldMethodMultiImport(null);
+    }
+}
diff --git a/lint-checks/integration-tests/src/main/java/replacewith/ReplaceWithUsageJava.java b/lint-checks/integration-tests/src/main/java/replacewith/ReplaceWithUsageJava.java
index 7afdee3..053663f 100644
--- a/lint-checks/integration-tests/src/main/java/replacewith/ReplaceWithUsageJava.java
+++ b/lint-checks/integration-tests/src/main/java/replacewith/ReplaceWithUsageJava.java
@@ -36,6 +36,31 @@
     }
 
     /**
+     * Calls the method on the object.
+     *
+     * @param obj The object on which to call the method.
+     * @deprecated Use {@link Object#toString()} directly.
+     */
+    @Deprecated
+    @ReplaceWith(expression = "obj.toString()", imports = "androidx.annotation.Deprecated")
+    public static void toStringWithImport(Object obj) {
+        // Stub.
+    }
+
+    /**
+     * Calls the method on the object.
+     *
+     * @param obj The object on which to call the method.
+     * @deprecated Use {@link Object#toString()} directly.
+     */
+    @Deprecated
+    @ReplaceWith(expression = "obj.toString()",
+            imports = {"androidx.annotation.Deprecated", "androidx.annotation.NonNull"})
+    public static void toStringWithImports(Object obj) {
+        // Stub.
+    }
+
+    /**
      * Returns a new object.
      */
     public static ReplaceWithUsageJava obtain(int param) {
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 0d8d683..10df47d 100644
--- a/lint-checks/src/main/java/androidx/build/lint/ReplaceWithDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/ReplaceWithDetector.kt
@@ -32,14 +32,19 @@
 import com.android.tools.lint.detector.api.Scope
 import com.android.tools.lint.detector.api.Severity
 import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.android.tools.lint.detector.api.isKotlin
 import com.intellij.psi.PsiField
+import com.intellij.psi.PsiJavaFile
 import com.intellij.psi.PsiMethod
 import com.intellij.psi.impl.source.tree.TreeElement
 import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.psi.KtFile
 import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry
 import org.jetbrains.kotlin.psi.psiUtil.endOffset
+import org.jetbrains.uast.UAnnotation
 import org.jetbrains.uast.UCallExpression
 import org.jetbrains.uast.UElement
+import org.jetbrains.uast.ULiteralExpression
 import org.jetbrains.uast.UQualifiedReferenceExpression
 import org.jetbrains.uast.USimpleNameReferenceExpression
 import org.jetbrains.uast.java.JavaConstructorUCallExpression
@@ -72,16 +77,16 @@
                 var expression = annotation.findAttributeValue("expression") ?.let { expr ->
                     ConstantEvaluator.evaluate(context, expr)
                 } as? String ?: return
-
                 val includeReceiver = Regex("^\\w+\\.\\w+.*\$").matches(expression)
                 val includeArguments = Regex("^.*\\w+\\(.*\\)$").matches(expression)
+                val imports = annotation.getAttributeValueVarargLiteral("imports")
 
                 if (referenced is PsiMethod && usage is UCallExpression) {
                     // Per Kotlin documentation for ReplaceWith: For function calls, the replacement
                     // expression may contain argument names of the deprecated function, which will
                     // be substituted with actual parameters used in the call being updated.
                     val argsToParams = referenced.parameters.mapIndexed { index, param ->
-                        param.name to usage.getArgumentForParameter(index)?.sourcePsi?.text
+                        param.name to usage.getArgumentForParameter(index)?.asSourceString()
                     }.associate { it }
 
                     // Tokenize the replacement expression using a regex, replacing as we go. This
@@ -126,7 +131,7 @@
                     }
                 }
 
-                reportLintFix(context, usage, location, expression)
+                reportLintFix(context, usage, location, expression, imports)
             }
         }
     }
@@ -136,13 +141,71 @@
         usage: UElement,
         location: Location,
         expression: String,
+        imports: List<String>,
     ) {
         context.report(ISSUE, usage, location, "Replacement available",
-            createLintFix(location, expression))
+            createLintFix(context, location, expression, imports))
     }
 
-    private fun createLintFix(location: Location, expression: String): LintFix =
-        fix().replace().range(location).name("Replace with `$expression`").with(expression).build()
+    private fun createLintFix(
+        context: JavaContext,
+        location: Location,
+        expression: String,
+        imports: List<String>
+    ): LintFix {
+        val lintFixBuilder = fix().composite()
+        lintFixBuilder.add(
+            fix()
+                .replace()
+                .range(location)
+                .name("Replace with `$expression`")
+                .with(expression)
+                .build()
+        )
+        if (imports.isNotEmpty()) {
+            lintFixBuilder.add(fix().import(context, add = imports).build())
+        }
+        return lintFixBuilder.build()
+    }
+
+    /**
+     * Add imports.
+     *
+     * @return a string replace builder
+     */
+    fun LintFix.Builder.import(
+        context: JavaContext,
+        add: List<String>
+    ): LintFix.ReplaceStringBuilder {
+        val isKotlin = isKotlin(context.uastFile!!.lang)
+        val lastImport = context.uastFile?.imports?.lastOrNull()
+        val packageElem = when (val psiFile = context.psiFile) {
+            is PsiJavaFile -> psiFile.packageStatement
+            is KtFile -> psiFile.packageDirective?.psiOrParent
+            else -> null
+        }
+
+        // Build the imports block. Leave any ordering or formatting up to the client.
+        val prependImports = when {
+            lastImport != null -> "\n"
+            packageElem != null -> "\n\n"
+            else -> ""
+        }
+        val appendImports = when {
+            lastImport != null -> ""
+            packageElem != null -> ""
+            else -> "\n"
+        }
+        val formattedImports = add.joinToString("\n") { "import " + if (isKotlin) it else "$it;" }
+        val importsText = prependImports + formattedImports + appendImports
+
+        // Append after any existing imports, after the package declaration, or at the beginning of
+        // the file if there are no imports and no package declaration.
+        val appendLocation = (lastImport ?: packageElem) ?.let { context.getLocation(it) }
+            ?: Location.create(context.file, context.getContents(), 0, 0)
+        val replaceBuilder = replace().range(appendLocation).end().with(importsText)
+        return replaceBuilder.autoFix()
+    }
 
     companion object {
         private val IMPLEMENTATION = Implementation(
@@ -230,3 +293,16 @@
 
     return getLocation(call)
 }
+
+/**
+ * @return the value of the specified vararg attribute as a list of String literals, or an empty
+ * list if not specified
+ */
+fun UAnnotation.getAttributeValueVarargLiteral(name: String): List<String> =
+    when (val attributeValue = findDeclaredAttributeValue(name)) {
+        is ULiteralExpression -> listOf(attributeValue.value.toString())
+        is UCallExpression -> attributeValue.valueArguments.mapNotNull { argument ->
+            (argument as? ULiteralExpression)?.value?.toString()
+        }
+        else -> emptyList()
+    }
diff --git a/lint-checks/src/test/java/androidx/build/lint/replacewith/ReplaceWithDetectorImportsTest.kt b/lint-checks/src/test/java/androidx/build/lint/replacewith/ReplaceWithDetectorImportsTest.kt
new file mode 100644
index 0000000..6788668
--- /dev/null
+++ b/lint-checks/src/test/java/androidx/build/lint/replacewith/ReplaceWithDetectorImportsTest.kt
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2019 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 ReplaceWithDetectorImportsTest {
+
+    @Test
+    fun methodWithImportsJava() {
+        val input = arrayOf(
+            javaSample("replacewith.MethodWithImportsJava")
+        )
+
+        /* ktlint-disable max-line-length */
+        val expected = """
+src/replacewith/MethodWithImportsJava.java:38: Information: Replacement available [ReplaceWith]
+        oldMethodSingleImport(null);
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+src/replacewith/MethodWithImportsJava.java:42: Information: Replacement available [ReplaceWith]
+        oldMethodMultiImport(null);
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 0 warnings
+        """.trimIndent()
+
+        val expectedFixDiffs = """
+Fix for src/replacewith/MethodWithImportsJava.java line 38: Replace with `newMethod(null)`:
+@@ -20 +20
++ import androidx.annotation.Deprecated;
+@@ -38 +39
+-         oldMethodSingleImport(null);
++         newMethod(null);
+Fix for src/replacewith/MethodWithImportsJava.java line 42: Replace with `newMethod(null)`:
+@@ -20 +20
++ import androidx.annotation.Deprecated;
++ import androidx.annotation.NonNull;
+@@ -42 +44
+-         oldMethodMultiImport(null);
++         newMethod(null);
+        """.trimIndent()
+        /* ktlint-enable max-line-length */
+
+        check(*input).expect(expected).expectFixDiffs(expectedFixDiffs)
+    }
+
+    @Test
+    fun methodWithImportsKotlin() {
+        val input = arrayOf(
+            ktSample("replacewith.MethodWithImportsKotlin"),
+            javaSample("replacewith.ReplaceWithUsageJava")
+        )
+
+        /* ktlint-disable max-line-length */
+        val expected = """
+src/replacewith/MethodWithImportsKotlin.kt:27: Information: Replacement available [ReplaceWith]
+        ReplaceWithUsageJava.toStringWithImport("hello")
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+src/replacewith/MethodWithImportsKotlin.kt:32: Information: Replacement available [ReplaceWith]
+        ReplaceWithUsageJava.toStringWithImports("world")
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 0 warnings
+        """.trimIndent()
+
+        val expectedFixDiffs = """
+Fix for src/replacewith/MethodWithImportsKotlin.kt line 27: Replace with `"hello".toString()`:
+@@ -19 +19
++ import androidx.annotation.Deprecated
+@@ -27 +28
+-         ReplaceWithUsageJava.toStringWithImport("hello")
++         "hello".toString()
+Fix for src/replacewith/MethodWithImportsKotlin.kt line 32: Replace with `"world".toString()`:
+@@ -19 +19
++ import androidx.annotation.Deprecated
++ import androidx.annotation.NonNull
+@@ -32 +34
+-         ReplaceWithUsageJava.toStringWithImports("world")
++         "world".toString()
+        """.trimIndent()
+        /* ktlint-enable max-line-length */
+
+        check(*input).expect(expected).expectFixDiffs(expectedFixDiffs)
+    }
+
+    @Test
+    fun methodWithNoImportsJava() {
+        val input = arrayOf(
+            javaSample("replacewith.MethodWithNoImportsJava")
+        )
+
+        /* ktlint-disable max-line-length */
+        val expected = """
+src/replacewith/MethodWithNoImportsJava.java:37: Information: Replacement available [ReplaceWith]
+        oldMethodSingleImport(null);
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+src/replacewith/MethodWithNoImportsJava.java:41: Information: Replacement available [ReplaceWith]
+        oldMethodMultiImport(null);
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 0 warnings
+        """.trimIndent()
+
+        val expectedFixDiffs = """
+Fix for src/replacewith/MethodWithNoImportsJava.java line 37: Replace with `newMethod(null)`:
+@@ -19 +19
++ import androidx.annotation.Deprecated;
++
+@@ -37 +39
+-         oldMethodSingleImport(null);
++         newMethod(null);
+Fix for src/replacewith/MethodWithNoImportsJava.java line 41: Replace with `newMethod(null)`:
+@@ -19 +19
++ import androidx.annotation.Deprecated;
++ import androidx.annotation.NonNull;
++
+@@ -41 +44
+-         oldMethodMultiImport(null);
++         newMethod(null);
+        """.trimIndent()
+        /* ktlint-enable max-line-length */
+
+        check(*input).expect(expected).expectFixDiffs(expectedFixDiffs)
+    }
+
+    @Test
+    fun methodWithNoImportsKotlin() {
+        val input = arrayOf(
+            ktSample("replacewith.MethodWithNoImportsKotlin"),
+            javaSample("replacewith.ReplaceWithUsageJava")
+        )
+
+        /* ktlint-disable max-line-length */
+        val expected = """
+src/replacewith/MethodWithNoImportsKotlin.kt:24: Information: Replacement available [ReplaceWith]
+        ReplaceWithUsageJava.toStringWithImport("hello")
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+src/replacewith/MethodWithNoImportsKotlin.kt:28: Information: Replacement available [ReplaceWith]
+        ReplaceWithUsageJava.toStringWithImports("world")
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 0 warnings
+        """.trimIndent()
+
+        val expectedFixDiffs = """
+Fix for src/replacewith/MethodWithNoImportsKotlin.kt line 24: Replace with `"hello".toString()`:
+@@ -18 +18
++ import androidx.annotation.Deprecated
++
+@@ -24 +26
+-         ReplaceWithUsageJava.toStringWithImport("hello")
++         "hello".toString()
+Fix for src/replacewith/MethodWithNoImportsKotlin.kt line 28: Replace with `"world".toString()`:
+@@ -18 +18
++ import androidx.annotation.Deprecated
++ import androidx.annotation.NonNull
++
+@@ -28 +31
+-         ReplaceWithUsageJava.toStringWithImports("world")
++         "world".toString()
+        """.trimIndent()
+        /* ktlint-enable max-line-length */
+
+        check(*input).expect(expected).expectFixDiffs(expectedFixDiffs)
+    }
+
+    @Test
+    fun methodWithNoImportsOrPackage() {
+        val input = arrayOf(
+            javaSample("replacewith.MethodWithNoImportsOrPackage")
+        )
+
+        /* ktlint-disable max-line-length */
+        val expected = """
+src/MethodWithNoImportsOrPackage.java:35: Information: Replacement available [ReplaceWith]
+        oldMethodSingleImport(null);
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+src/MethodWithNoImportsOrPackage.java:39: Information: Replacement available [ReplaceWith]
+        oldMethodMultiImport(null);
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 0 warnings
+        """.trimIndent()
+
+        val expectedFixDiffs = """
+Fix for src/MethodWithNoImportsOrPackage.java line 35: Replace with `newMethod(null)`:
+@@ -1 +1
++ import androidx.annotation.Deprecated;
+@@ -35 +36
+-         oldMethodSingleImport(null);
++         newMethod(null);
+Fix for src/MethodWithNoImportsOrPackage.java line 39: Replace with `newMethod(null)`:
+@@ -1 +1
++ import androidx.annotation.Deprecated;
++ import androidx.annotation.NonNull;
+@@ -39 +41
+-         oldMethodMultiImport(null);
++         newMethod(null);
+        """.trimIndent()
+        /* ktlint-enable max-line-length */
+
+        check(*input).expect(expected).expectFixDiffs(expectedFixDiffs)
+    }
+}
diff --git a/lint-checks/src/test/java/androidx/build/lint/replacewith/TestUtils.kt b/lint-checks/src/test/java/androidx/build/lint/replacewith/TestUtils.kt
index 15dcffb..ff498bb 100644
--- a/lint-checks/src/test/java/androidx/build/lint/replacewith/TestUtils.kt
+++ b/lint-checks/src/test/java/androidx/build/lint/replacewith/TestUtils.kt
@@ -26,6 +26,7 @@
     return TestLintTask.lint()
         .files(
             ANDROIDX_REPLACE_WITH_KT,
+            ANDROIDX_ANY_THREAD_KT,
             *testFiles
         )
         .issues(ReplaceWithDetector.ISSUE)
@@ -55,7 +56,7 @@
 }
 
 /**
- * [TestFile] containing ReplaceWith.kt from the ReplaceWith annotation library.
+ * [TestFile] containing ReplaceWith.kt from the Annotation library.
  *
  * This is a workaround for IntelliJ failing to recognize source files if they are also
  * included as resources.
@@ -87,3 +88,25 @@
             )
             """.trimIndent()
 )
+
+/**
+ * [TestFile] containing AnyThread.kt from the Annotation library.
+ */
+val ANDROIDX_ANY_THREAD_KT: TestFile = TestFiles.kotlin(
+    """
+            package androidx.annotation
+            
+            @MustBeDocumented
+            @Retention(AnnotationRetention.BINARY)
+            @Target(
+                AnnotationTarget.FUNCTION,
+                AnnotationTarget.PROPERTY_GETTER,
+                AnnotationTarget.PROPERTY_SETTER,
+                AnnotationTarget.CONSTRUCTOR,
+                AnnotationTarget.ANNOTATION_CLASS,
+                AnnotationTarget.CLASS,
+                AnnotationTarget.VALUE_PARAMETER
+            )
+            annotation class AnyThread
+            """.trimIndent()
+)