Lint rules for WorkManager
* This lint rule catches improper implementations of Configuration.Provider
Test: Added unit tests.
Change-Id: I2ae376d99080cdf187b8a1ec3bd1b14e139008ee
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index 123d247..c10b3b3 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -143,7 +143,7 @@
.addStubs("wear/wear_stubs/com.google.android.wearable-stubs.jar")
prebuilts(LibraryGroups.WEBKIT, "1.1.0")
ignore(LibraryGroups.WORK.group, "work-gcm")
- ignore(LibraryGroups.WORK.group, "work-foreground")
+ ignore(LibraryGroups.WORK.group, "work-runtime-lint")
prebuilts(LibraryGroups.WORK, "2.3.0-alpha03")
default(Ignore)
}
diff --git a/settings.gradle b/settings.gradle
index b7a1a00..0a90373 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -265,6 +265,7 @@
includeProject(":work:work-runtime-ktx", "work/workmanager-ktx")
includeProject(":work:work-rxjava2", "work/workmanager-rxjava2")
includeProject(":work:work-testing", "work/workmanager-testing")
+includeProject(":work:work-runtime-lint", "work/workmanager-lint")
includeProject(":work:work-benchmark", "work/workmanager-benchmark")
includeProject(":work:integration-tests:testapp", "work/integration-tests/testapp")
diff --git a/work/workmanager-lint/build.gradle b/work/workmanager-lint/build.gradle
new file mode 100644
index 0000000..7abb2b9
--- /dev/null
+++ b/work/workmanager-lint/build.gradle
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.AndroidXExtension
+import androidx.build.CompilationTarget
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SdkHelperKt
+import androidx.build.Publish
+
+plugins {
+ id("AndroidXPlugin")
+ id("kotlin")
+}
+
+ext.generatedResources = "$buildDir/generated/sdkResourcesForTest"
+
+sourceSets {
+ test.resources.srcDirs += generatedResources
+}
+
+task generateSdkResource() {
+ outputs.dir(generatedResources)
+ doLast {
+ new File(generatedResources, "sdk.prop").withWriter('UTF-8') { writer ->
+ writer.write("sdk.dir=${SdkHelperKt.getSdkPath(project.rootDir)}")
+ }
+ }
+}
+
+tasks["compileTestJava"].dependsOn generateSdkResource
+
+dependencies {
+ // compileOnly because we use lintChecks and it doesn't allow other types of deps
+ // this ugly hack exists because of b/63873667
+ if (rootProject.hasProperty("android.injected.invoked.from.ide")) {
+ compileOnly LINT_API_LATEST
+ } else {
+ compileOnly LINT_API_MIN
+ }
+ compileOnly KOTLIN_STDLIB
+
+ testImplementation KOTLIN_STDLIB
+ testImplementation LINT_CORE
+ testImplementation LINT_TESTS
+}
+
+androidx {
+ name = "Android WorkManager Runtime Lint Checks"
+ toolingProject = true
+ publish = Publish.NONE
+ mavenVersion = LibraryVersions.WORK
+ mavenGroup = LibraryGroups.WORK
+ inceptionYear = "2019"
+ description = "Android WorkManager Runtime Lint Checks"
+ url = AndroidXExtension.ARCHITECTURE_URL
+ compilationTarget = CompilationTarget.HOST
+}
diff --git a/work/workmanager-lint/src/main/java/androidx/work/lint/BadConfigurationProviderIssueDetector.kt b/work/workmanager-lint/src/main/java/androidx/work/lint/BadConfigurationProviderIssueDetector.kt
new file mode 100644
index 0000000..c8475ef
--- /dev/null
+++ b/work/workmanager-lint/src/main/java/androidx/work/lint/BadConfigurationProviderIssueDetector.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.Context
+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.Location
+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 org.jetbrains.uast.UClass
+
+class BadConfigurationProviderIssueDetector : Detector(), SourceCodeScanner {
+ companion object {
+ val ISSUE = Issue.create(
+ id = "BadConfigurationProvider",
+ briefDescription = "Invalid WorkManager Configuration Provider",
+ explanation = """
+ An `android.app.Application` must implement `androidx.work.Configuration.Provider`
+ for on-demand initialization.
+ """,
+ androidSpecific = true,
+ category = Category.CORRECTNESS,
+ severity = Severity.FATAL,
+ implementation = Implementation(
+ BadConfigurationProviderIssueDetector::class.java, Scope.JAVA_FILE_SCOPE
+ )
+ )
+ }
+
+ private var hasApplicableTypes = false
+ private var correct = false
+ private var location: Location? = null
+
+ override fun applicableSuperClasses() = listOf(
+ "android.app.Application",
+ "androidx.work.Configuration.Provider"
+ )
+
+ override fun visitClass(context: JavaContext, declaration: UClass) {
+ if (correct) {
+ // Bail out early
+ return
+ }
+
+ val name = declaration.qualifiedName
+ if (name == "androidx.work.Configuration.Provider" || name == "android.app.Application") {
+ // Exempt base types from analysis
+ return
+ }
+
+ val isApplication = context.evaluator.inheritsFrom(
+ declaration.javaPsi, "android.app.Application", true
+ )
+
+ val isProvider = context.evaluator.inheritsFrom(
+ declaration.javaPsi, "androidx.work.Configuration.Provider", true
+ )
+
+ if (isApplication) {
+ location = Location.create(context.file)
+ }
+
+ if (isProvider) {
+ hasApplicableTypes = true
+ }
+
+ if (isApplication && isProvider) {
+ correct = true
+ }
+ }
+
+ override fun afterCheckRootProject(context: Context) {
+ if (hasApplicableTypes && !correct) {
+ context.report(
+ issue = ISSUE,
+ location = location ?: Location.create(context.file),
+ message = "Expected Application subtype to implement Configuration.Provider"
+ )
+ }
+ }
+}
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
new file mode 100644
index 0000000..8a42724
--- /dev/null
+++ b/work/workmanager-lint/src/main/java/androidx/work/lint/WorkManagerIssueRegistry.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.client.api.IssueRegistry
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.android.tools.lint.detector.api.Issue
+
+class WorkManagerIssueRegistry : IssueRegistry() {
+ override val minApi: Int = CURRENT_API
+ override val issues: List<Issue> = listOf(
+ BadConfigurationProviderIssueDetector.ISSUE
+ )
+}
diff --git a/work/workmanager-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry b/work/workmanager-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
new file mode 100644
index 0000000..ef57dd0
--- /dev/null
+++ b/work/workmanager-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
@@ -0,0 +1 @@
+androidx.work.lint.WorkManagerIssueRegistry
\ No newline at end of file
diff --git a/work/workmanager-lint/src/test/java/androidx/work/lint/BadConfigurationProviderTest.kt b/work/workmanager-lint/src/test/java/androidx/work/lint/BadConfigurationProviderTest.kt
new file mode 100644
index 0000000..2a1f43a
--- /dev/null
+++ b/work/workmanager-lint/src/test/java/androidx/work/lint/BadConfigurationProviderTest.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.ANDROID_APPLICATION
+import androidx.work.lint.Stubs.WORK_MANAGER_CONFIGURATION_PROVIDER
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest.kotlin
+import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
+import org.junit.Test
+
+class BadConfigurationProviderTest {
+ @Test
+ fun testNoConfigurationProviderUsage() {
+ val customApplication = kotlin(
+ "com/example/App.kt",
+ """
+ package com.example
+
+ import android.app.Application
+
+ class App: Application() {
+ override fun onCreate() {
+
+ }
+ }
+ """
+ ).indented().within("src")
+
+ lint().files(
+ ANDROID_APPLICATION,
+ WORK_MANAGER_CONFIGURATION_PROVIDER,
+ customApplication
+ )
+ .issues(BadConfigurationProviderIssueDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun testWithInvalidConfigurationProvider() {
+ val customApplication = kotlin(
+ "com/example/App.kt",
+ """
+ package com.example
+
+ import android.app.Application
+
+ class App: Application() {
+ override fun onCreate() {
+
+ }
+ }
+ """
+ ).indented().within("src")
+
+ val invalidProvider = kotlin(
+ "com/example/CustomProvider.kt",
+ """
+ package com.example
+
+ import androidx.work.Configuration
+
+ class Provider: Configuration.Provider {
+ override fun getWorkManagerConfiguration(): Configuration = TODO()
+ }
+ """
+ ).indented().within("src")
+
+ /* ktlint-disable max-line-length */
+ lint().files(
+ ANDROID_APPLICATION,
+ WORK_MANAGER_CONFIGURATION_PROVIDER,
+ customApplication,
+ invalidProvider
+ )
+ .issues(BadConfigurationProviderIssueDetector.ISSUE)
+ .run()
+ .expect("""
+ src/com/example/App.kt: Error: Expected Application subtype to implement Configuration.Provider [BadConfigurationProvider]
+ 1 errors, 0 warnings
+ """.trimIndent())
+ /* ktlint-enable max-line-length */
+ }
+
+ @Test
+ fun testWithValidConfigurationProvider() {
+ val customApplication = kotlin(
+ "com/example/App.kt",
+ """
+ package com.example
+
+ import android.app.Application
+ import androidx.work.Configuration
+
+ class App: Application(), Configuration.Provider {
+ override fun onCreate() {
+
+ }
+
+ override fun getWorkManagerConfiguration(): Configuration = TODO()
+ }
+ """
+ ).indented().within("src")
+
+ lint().files(
+ ANDROID_APPLICATION,
+ WORK_MANAGER_CONFIGURATION_PROVIDER,
+ customApplication
+ )
+ .issues(BadConfigurationProviderIssueDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+}
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
new file mode 100644
index 0000000..6b158d2c
--- /dev/null
+++ b/work/workmanager-lint/src/test/java/androidx/work/lint/Stubs.kt
@@ -0,0 +1,48 @@
+/*
+ * 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 com.android.tools.lint.checks.infrastructure.LintDetectorTest.kotlin
+import com.android.tools.lint.checks.infrastructure.TestFile
+
+object Stubs {
+ val WORK_MANAGER_CONFIGURATION_PROVIDER: TestFile = kotlin(
+ "androidx/work/Configuration.kt",
+ """
+ package androidx.work
+ class Configuration {
+ interface Provider {
+ fun getWorkManagerConfiguration(): Configuration
+ }
+ }
+ """
+ )
+ .indented().within("src")
+
+ val ANDROID_APPLICATION: TestFile = kotlin(
+ "android/app/Application.kt",
+ """
+ package android.app
+ open class Application {
+ fun onCreate() {
+
+ }
+ }
+ """
+ )
+ .indented().within("src")
+}
\ No newline at end of file