Add Lint rules that warn when using enqueue() for `PeriodicWorkRequest`s.

* The lint rule suggests that they use `enqueueUniquePeriodicWork` instead.

Test: Added unit tests.
Change-Id: I1047b86d5e7a1ad4139c17dff0665c001833b68f
diff --git a/work/workmanager-lint/src/main/java/androidx/work/lint/PeriodicEnqueueIssueDetector.kt b/work/workmanager-lint/src/main/java/androidx/work/lint/PeriodicEnqueueIssueDetector.kt
new file mode 100644
index 0000000..3f14013
--- /dev/null
+++ b/work/workmanager-lint/src/main/java/androidx/work/lint/PeriodicEnqueueIssueDetector.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.work.lint
+
+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.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+class PeriodicEnqueueIssueDetector : Detector(), SourceCodeScanner {
+    companion object {
+        val ISSUE = Issue.create(
+            id = "BadPeriodicWorkRequestEnqueue",
+            briefDescription = "Use `enqueueUniquePeriodicWork()` instead of `enqueue()`",
+            explanation = """
+                When using `enqueue()` for `PeriodicWorkRequest`s, you might end up enqueuing
+                duplicate requests unintentionally. You should be using
+                `enqueueUniquePeriodicWork` with an `ExistingPeriodicWorkPolicy.KEEP` instead.
+            """,
+            androidSpecific = true,
+            category = Category.CORRECTNESS,
+            severity = Severity.WARNING,
+            implementation = Implementation(
+                PeriodicEnqueueIssueDetector::class.java, Scope.JAVA_FILE_SCOPE
+            )
+        )
+    }
+
+    override fun getApplicableMethodNames(): List<String>? = listOf("enqueue")
+
+    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+        if (context.evaluator.isMemberInClass(method, "androidx.work.WorkManager")) {
+            val periodic = node.valueArguments.filter { argument ->
+                val type = argument.getExpressionType()?.canonicalText
+                type == "androidx.work.PeriodicWorkRequest" ||
+                        type == "java.util.List<? extends androidx.work.PeriodicWorkRequest>" ||
+                        type == "java.util.List<? extends androidx.work.WorkRequest>"
+            }
+            if (periodic.isNotEmpty()) {
+                context.report(
+                    ISSUE,
+                    context.getLocation(method),
+                    message = "Use `enqueueUniquePeriodicWork()` instead of `enqueue()`"
+                )
+            }
+        }
+    }
+}
diff --git a/work/workmanager-lint/src/main/java/androidx/work/lint/WorkManagerIssueRegistry.kt b/work/workmanager-lint/src/main/java/androidx/work/lint/WorkManagerIssueRegistry.kt
index 8a42724..8e69bab 100644
--- a/work/workmanager-lint/src/main/java/androidx/work/lint/WorkManagerIssueRegistry.kt
+++ b/work/workmanager-lint/src/main/java/androidx/work/lint/WorkManagerIssueRegistry.kt
@@ -25,6 +25,7 @@
 class WorkManagerIssueRegistry : IssueRegistry() {
     override val minApi: Int = CURRENT_API
     override val issues: List<Issue> = listOf(
-        BadConfigurationProviderIssueDetector.ISSUE
+        BadConfigurationProviderIssueDetector.ISSUE,
+        PeriodicEnqueueIssueDetector.ISSUE
     )
 }
diff --git a/work/workmanager-lint/src/test/java/androidx/work/lint/PeriodicEnqueueIssueDetectorTest.kt b/work/workmanager-lint/src/test/java/androidx/work/lint/PeriodicEnqueueIssueDetectorTest.kt
new file mode 100644
index 0000000..28e1cf4
--- /dev/null
+++ b/work/workmanager-lint/src/test/java/androidx/work/lint/PeriodicEnqueueIssueDetectorTest.kt
@@ -0,0 +1,211 @@
+/*
+ * 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.work.lint
+
+import androidx.work.lint.Stubs.ONE_TIME_WORK_REQUEST
+import androidx.work.lint.Stubs.PERIODIC_WORK_REQUEST
+import androidx.work.lint.Stubs.WORK_MANAGER
+import androidx.work.lint.Stubs.WORK_REQUEST
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest.kotlin
+import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
+import org.junit.Test
+
+class PeriodicEnqueueIssueDetectorTest {
+
+    @Test
+    fun warnWhenEnqueueingPeriodicWork() {
+        val snippet = kotlin(
+            "com/example/Snippet.kt",
+            """
+            package com.example
+
+            import androidx.work.PeriodicWorkRequest
+            import androidx.work.WorkManager
+
+            class Test {
+                fun enqueueWork() {
+                    val request = PeriodicWorkRequest()
+                    val workManager: WorkManager = TODO("Get an implementation of WorkManager")
+                    workManager.enqueue(request)
+                }
+            }
+            """
+        ).indented().within("src")
+
+        /* ktlint-disable max-line-length */
+        lint().files(
+            WORK_MANAGER,
+            WORK_REQUEST,
+            ONE_TIME_WORK_REQUEST,
+            PERIODIC_WORK_REQUEST,
+            snippet
+        )
+            .issues(PeriodicEnqueueIssueDetector.ISSUE)
+            .run()
+            .expect("""
+                src/androidx/work/WorkManager.kt:4: Warning: Use enqueueUniquePeriodicWork() instead of enqueue() [BadPeriodicWorkRequestEnqueue]
+                   fun enqueue(request: WorkRequest)
+                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                0 errors, 1 warnings
+            """.trimIndent())
+        /* ktlint-enable max-line-length */
+    }
+
+    @Test
+    fun warnWhenEnqueuingListOfPeriodicWork() {
+        val snippet = kotlin(
+            "com/example/Snippet.kt",
+            """
+            package com.example
+
+            import androidx.work.PeriodicWorkRequest
+            import androidx.work.WorkManager
+
+            class Test {
+                fun enqueueWork() {
+                    val requests = listOf(PeriodicWorkRequest())
+                    val workManager: WorkManager = TODO("Get an implementation of WorkManager")
+                    workManager.enqueue(requests)
+                }
+            }
+            """
+        ).indented().within("src")
+
+        /* ktlint-disable max-line-length */
+        lint().files(
+            WORK_MANAGER,
+            WORK_REQUEST,
+            ONE_TIME_WORK_REQUEST,
+            PERIODIC_WORK_REQUEST,
+            snippet
+        )
+            .issues(PeriodicEnqueueIssueDetector.ISSUE)
+            .run()
+            .expect("""
+                src/androidx/work/WorkManager.kt:5: Warning: Use enqueueUniquePeriodicWork() instead of enqueue() [BadPeriodicWorkRequestEnqueue]
+                   fun enqueue(requests: List<WorkRequest>)
+                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                0 errors, 1 warnings
+            """.trimIndent())
+        /* ktlint-enable max-line-length */
+    }
+
+    @Test
+    fun warnWhenEnqueuingListOfPeriodicWorkAndOneTimeWork() {
+        val snippet = kotlin(
+            "com/example/Snippet.kt",
+            """
+            package com.example
+
+            import androidx.work.WorkRequest
+            import androidx.work.OneTimeWorkRequest
+            import androidx.work.PeriodicWorkRequest
+            import androidx.work.WorkManager
+
+            class Test {
+                fun enqueueWork() {
+                    val requests = listOf(OneTimeWorkRequest(), PeriodicWorkRequest())
+                    val workManager: WorkManager = TODO("Get an implementation of WorkManager")
+                    workManager.enqueue(requests)
+                }
+            }
+            """
+        ).indented().within("src")
+
+        /* ktlint-disable max-line-length */
+        lint().files(
+            WORK_MANAGER,
+            WORK_REQUEST,
+            ONE_TIME_WORK_REQUEST,
+            PERIODIC_WORK_REQUEST,
+            snippet
+        )
+            .issues(PeriodicEnqueueIssueDetector.ISSUE)
+            .run()
+            .expect("""
+                src/androidx/work/WorkManager.kt:5: Warning: Use enqueueUniquePeriodicWork() instead of enqueue() [BadPeriodicWorkRequestEnqueue]
+                   fun enqueue(requests: List<WorkRequest>)
+                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                0 errors, 1 warnings
+            """.trimIndent())
+        /* ktlint-enable max-line-length */
+    }
+
+    @Test
+    fun noWarningsWhenUsingOneTimeWorkRequest() {
+        val snippet = kotlin(
+            "com/example/Snippet.kt",
+            """
+            package com.example
+
+            import androidx.work.PeriodicWorkRequest
+            import androidx.work.WorkManager
+
+            class Test {
+                fun enqueueWork() {
+                    val request = OneTimeWorkRequest()
+                    val workManager: WorkManager = TODO("Get an implementation of WorkManager")
+                    workManager.enqueue(request)
+                }
+            }
+            """
+        ).indented().within("src")
+
+        lint().files(
+            WORK_MANAGER,
+            WORK_REQUEST,
+            ONE_TIME_WORK_REQUEST,
+            PERIODIC_WORK_REQUEST,
+            snippet
+        )
+            .issues(PeriodicEnqueueIssueDetector.ISSUE)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun noWarningsWhenEnqueuingListOfOneTimeWorkRequests() {
+        val snippet = kotlin(
+            "com/example/Snippet.kt",
+            """
+            package com.example
+
+            import androidx.work.PeriodicWorkRequest
+            import androidx.work.WorkManager
+
+            class Test {
+                fun enqueueWork() {
+                    val requests = listOf(OneTimeWorkRequest())
+                    val workManager: WorkManager = TODO("Get an implementation of WorkManager")
+                    workManager.enqueue(requests)
+                }
+            }
+            """
+        ).indented().within("src")
+
+        lint().files(
+            WORK_MANAGER,
+            WORK_REQUEST,
+            ONE_TIME_WORK_REQUEST,
+            PERIODIC_WORK_REQUEST,
+            snippet
+        )
+            .issues(PeriodicEnqueueIssueDetector.ISSUE)
+            .run()
+            .expectClean()
+    }
+}
\ No newline at end of file
diff --git a/work/workmanager-lint/src/test/java/androidx/work/lint/Stubs.kt b/work/workmanager-lint/src/test/java/androidx/work/lint/Stubs.kt
index 6b158d2c..1034d25 100644
--- a/work/workmanager-lint/src/test/java/androidx/work/lint/Stubs.kt
+++ b/work/workmanager-lint/src/test/java/androidx/work/lint/Stubs.kt
@@ -45,4 +45,45 @@
             """
     )
         .indented().within("src")
-}
\ No newline at end of file
+
+    val WORK_REQUEST: TestFile = kotlin(
+        "androidx/work/WorkRequest.kt",
+        """
+            package androidx.work
+
+            open class WorkRequest
+        """
+    ).indented().within("src")
+
+    val ONE_TIME_WORK_REQUEST: TestFile = kotlin(
+        "androidx/work/OneTimeWorkRequest.kt",
+        """
+            package androidx.work
+
+            class OneTimeWorkRequest: WorkRequest()
+        """
+    ).indented().within("src")
+
+    val PERIODIC_WORK_REQUEST: TestFile = kotlin(
+        "androidx/work/PeriodicWorkRequest.kt",
+        """
+            package androidx.work
+
+            class PeriodicWorkRequest: WorkRequest()
+        """
+    ).indented().within("src")
+
+    val WORK_MANAGER: TestFile = kotlin(
+        "androidx/work/WorkManager.kt",
+        """
+                 package androidx.work
+
+                 interface WorkManager {
+                    fun enqueue(request: WorkRequest)
+                    fun enqueue(requests: List<WorkRequest>)
+                    fun enqueueUniqueWork(name: String, request: PeriodicWorkRequest)
+                 }
+            """
+    )
+        .indented().within("src")
+}