Add a lint check for directly creating a ViewModel in a composable

Test: ViewModelConstructorInComposableTest
Bug: NA
Change-Id: I6cbedb7931b9f6b6edf44177673e4ec316fc8a87
diff --git a/lifecycle/lifecycle-viewmodel-compose-lint/build.gradle b/lifecycle/lifecycle-viewmodel-compose-lint/build.gradle
new file mode 100644
index 0000000..ab1344d
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel-compose-lint/build.gradle
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+/**
+ * This file was created using the `create_project.py` script located in the
+ * `<AndroidX root>/development/project-creator` directory.
+ *
+ * Please use that script when creating a new project, rather than copying an existing project and
+ * modifying its settings.
+ */
+
+import androidx.build.BundleInsideHelper
+import androidx.build.LibraryType
+
+plugins {
+    id("AndroidXPlugin")
+    id("kotlin")
+}
+
+BundleInsideHelper.forInsideLintJar(project)
+
+dependencies {
+    compileOnly(libs.androidLintMinApi)
+    compileOnly(libs.kotlinStdlib)
+    bundleInside(projectOrArtifact(":compose:lint:common"))
+
+    testImplementation(projectOrArtifact(":compose:lint:common-test"))
+    testImplementation(libs.kotlinStdlib)
+    testImplementation(libs.kotlinReflect)
+    testImplementation(libs.kotlinStdlibJdk8)
+    testImplementation(libs.androidLint)
+    testImplementation(libs.androidLintTests)
+    testImplementation(libs.junit)
+}
+
+androidx {
+    name = "Lifecycle ViewModel Compose Lint Checks"
+    type = LibraryType.LINT
+    inceptionYear = "2024"
+    description = "Android Lifecycles Lint Checks"
+}
diff --git a/lifecycle/lifecycle-viewmodel-compose-lint/src/main/java/androidx/lifecycle/lint/LifecycleViewModelComposeIssueRegistry.kt b/lifecycle/lifecycle-viewmodel-compose-lint/src/main/java/androidx/lifecycle/lint/LifecycleViewModelComposeIssueRegistry.kt
new file mode 100644
index 0000000..2e1099f
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel-compose-lint/src/main/java/androidx/lifecycle/lint/LifecycleViewModelComposeIssueRegistry.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.lifecycle.lint
+
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.client.api.Vendor
+import com.android.tools.lint.detector.api.CURRENT_API
+
+@Suppress("UnstableApiUsage")
+class LifecycleViewModelComposeIssueRegistry : IssueRegistry() {
+    // tests are run with this version. We ensure that with ApiLintVersionsTest
+    override val api = 14
+    override val minApi = CURRENT_API
+    override val issues
+        get() = listOf(ViewModelConstructorInComposableDetector.ISSUE)
+
+    override val vendor =
+        Vendor(
+            feedbackUrl = "https://issuetracker.google.com/issues/new?component=413132",
+            identifier = "androidx.lifecycle",
+            vendorName = "Android Open Source Project",
+        )
+}
diff --git a/lifecycle/lifecycle-viewmodel-compose-lint/src/main/java/androidx/lifecycle/lint/ViewModelConstructorInComposableDetector.kt b/lifecycle/lifecycle-viewmodel-compose-lint/src/main/java/androidx/lifecycle/lint/ViewModelConstructorInComposableDetector.kt
new file mode 100644
index 0000000..0114cd8
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel-compose-lint/src/main/java/androidx/lifecycle/lint/ViewModelConstructorInComposableDetector.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.lifecycle.lint
+
+import androidx.compose.lint.Name
+import androidx.compose.lint.Package
+import androidx.compose.lint.inheritsFrom
+import androidx.compose.lint.isInvokedWithinComposable
+import com.android.tools.lint.client.api.UElementHandler
+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 java.util.EnumSet
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.util.isConstructorCall
+
+/** [Detector] that checks if a view model is being constructed directly in a composable. */
+class ViewModelConstructorInComposableDetector : Detector(), SourceCodeScanner {
+    override fun getApplicableUastTypes(): List<Class<out UElement>> {
+        return listOf(UCallExpression::class.java)
+    }
+
+    override fun createUastHandler(context: JavaContext): UElementHandler {
+        return object : UElementHandler() {
+            override fun visitCallExpression(node: UCallExpression) {
+                if (!node.isInvokedWithinComposable()) return
+                if (!node.isConstructorCall()) return
+
+                val containingClass = node.resolve()?.containingClass ?: return
+                if (containingClass.inheritsFrom(FqViewModelName)) {
+                    context.report(
+                        ISSUE,
+                        node,
+                        context.getNameLocation(node),
+                        "Constructing a view model in a composable"
+                    )
+                }
+            }
+        }
+    }
+
+    companion object {
+        private val FqViewModelName = Name(Package("androidx.lifecycle"), "ViewModel")
+
+        val ISSUE =
+            Issue.create(
+                "ViewModelConstructorInComposable",
+                "Constructing a view model in a composable",
+                "View models should not be constructed directly inside composable" +
+                    " functions. Instead you should use the lifecycle viewmodel extension" +
+                    "functions e.g. viewModel<MyViewModel>()",
+                Category.CORRECTNESS,
+                3,
+                Severity.ERROR,
+                Implementation(
+                    ViewModelConstructorInComposableDetector::class.java,
+                    EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
+                )
+            )
+    }
+}
diff --git a/lifecycle/lifecycle-viewmodel-compose-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry b/lifecycle/lifecycle-viewmodel-compose-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
new file mode 100644
index 0000000..3dc32ba
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel-compose-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
@@ -0,0 +1 @@
+androidx.lifecycle.lint.LifecycleViewModelComposeIssueRegistry
\ No newline at end of file
diff --git a/lifecycle/lifecycle-viewmodel-compose-lint/src/test/java/androidx/lifecycle/viewmodel/compose/lint/Stubs.kt b/lifecycle/lifecycle-viewmodel-compose-lint/src/test/java/androidx/lifecycle/viewmodel/compose/lint/Stubs.kt
new file mode 100644
index 0000000..997e846
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel-compose-lint/src/test/java/androidx/lifecycle/viewmodel/compose/lint/Stubs.kt
@@ -0,0 +1,215 @@
+/*
+ * 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.lifecycle.viewmodel.compose.lint.lint
+
+import androidx.compose.lint.test.bytecodeStub
+
+internal val VIEWMODEL =
+    bytecodeStub(
+        filename = "ViewModel.kt",
+        filepath = "androidx/lifecycle",
+        checksum = 0xacdef33f,
+        source =
+            """
+                package androidx.lifecycle
+                public open class ViewModel
+
+                public class ViewModelStoreOwner
+                public class ViewModelProvider {
+                   public class Factory {
+                        public fun <T : ViewModel> create(): T {
+                            return MockStore<T>().vm
+                        }
+
+                        private class MockStore<T : ViewModel> {
+                            lateinit var vm: T
+                        }
+                   }
+                }
+                public class CreationExtras
+            """
+                .trimIndent(),
+        """
+                META-INF/main.kotlin_module:
+                H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijgMuZSScxLKcrPTKnQy8lMS02uTM5J
+                1SvLTC3PzU9JzdFLzs8tyC9OFeIOAwr5goS8S7hkuLiB4nqpFYm5BTmpQry+
+                lUiySgxaDABrUUXUawAAAA==
+                """,
+        """
+                androidx/lifecycle/CreationExtras.class:
+                H4sIAAAAAAAA/4VRTS8DQRh+3qluWcXWZ30m4oKDLXEjEoSkSZEgvThNdwej
+                29lkd0rd+lv8AyeJgzSOfpR4d7m7PHk+3pk878zX9/sHgF0sE1alCZNYhz0/
+                0rcqeA4i5R8nSlodm5OeTWRaAhG8B/ko/UiaO/+i9aACW0KB4Oxro+0BobC+
+                0SyjCMfFEEqEIXuvU8Ja49/b9wiVRju2kTb+mbIylFayJzqPBa5IGYxkAAK1
+                2e/pTNWYhduElUHfdUVVuMJjNuhXB/0dUaOj4ueLIzyRTe1Qdrbc1OrpLA5V
+                tNW23O+YKWGioY0673ZaKrmWrYidyUYcyKgpE53pP9O9irtJoE51JuYvu8bq
+                jmrqVHN6aExs831SbEPw+n9ls9dgrLLycw0UN98w/MpEYJ7RyU0PC4zl3wGM
+                wM3zxRznsJT/E2GUs/INCnWM1TFexwQ8pqjUMYmpG1CKacxwnsJNMZvC+QGU
+                yd0P5AEAAA==
+                """,
+        """
+                androidx/lifecycle/ViewModel.class:
+                H4sIAAAAAAAA/31RTS8DQRh+3qlua5W2Pqo+ww0Hi7gRCRJJk5YE6cVpujuY
+                djubdKfFrb/FP3CSOEjj6EeJd5c4ujx5Pt6ZeWbm8+vtHcA+VgjL0gS9SAeP
+                Xqhvlf/kh8pravXQiAIV5kCEUlsOpBdKc+ddtNrKtzlkCM6hNtoeETIbm80C
+                snBcjCFHGLP3Oias1v/b+IBQrnciG2rjNZSVgbSSPdEdZLgYJTCeAAjUYf9R
+                J2qHWbBLWBsNXVdUhStKzEbDfKU6Gu6JHTrJfjw7oiSSuT1KVhf+jtzuWC53
+                ypRQrGujzvvdlupdy1bIznQ98mXYlD2d6F/TvYr6PV+d6UQsXPaN1V3V1LHm
+                9NiYyEqrIxNjHYLv/ls3eQrGKisv1UB26xX5FyYCC4xOagosMhZ+BjAON/WW
+                UpzHcvo/hAnOCjfI1DBZw1QNRZSYolzDNGZuQDFmMcd5DDdGJYbzDcrOclbc
+                AQAA
+                """,
+        """
+                androidx/lifecycle/ViewModelProvider$Factory$MockStore.class:
+                H4sIAAAAAAAA/5VUW28TRxT+Zn3ZtXHK2hSahJAGSIvtQDakKU1JGggpVJac
+                FGFjWuVpsp46E693o92xSfrkp/6Q/gIqtSrqQ2Xx0If+KNQz9ioNNwsk68y5
+                fOc7Z86c9b8v//obwAruM9zifjMMZPPI8eRPwj12PeE0pHi6HTSF9zAMerIp
+                wvkH3FVBeDy/HbjtGmnCBGNYWa/fro7LX9uoHvAedzzut5zv9w6Eq9YY7Nd9
+                JpIM6XXpS7XBkCiWGjmkYWaRgsWQVPsyYlgdW+ndnVLBVEuoRodhrlga3y5B
+                i6V6nc6r1SBsOQdC7YVc+pHDfT9QXMmA9J1A7XQ9jTZ6xDo7njMHG/kMDBQs
+                2IRuB8qTvnPQ6zjSVyL0uedUfBVSFelGJs4zlNR+GDx97OuBSO7Jn0WT7nco
+                QnW86boiiu4fueJQN8NwvnhqxDVN01rT8/sEk1lcwBTDzLj2TFykS0ej+Vwu
+                jr9KqUHPVKT5aMVcp6wbdzYszFEX7r5w2/FgHvKQdwRdjeFa8c0FeHu/V3BV
+                9ztP7z98gHw1HtS2ULzJFdfj7vQStLdMi4wWYGBt8h9JbS2R1rzJUB/0C1lj
+                0sgO+sPDsLVimZYxOeiXLWvQt1nZWDKWjaXEvdSLX9OGnXw0Y6emjdVB/4cX
+                vyySy6as7HTSStvmlaRl2RnNvUzl6kxXvf4hu2hihWH+fTJM3KLBxmkMmZMl
+                ZsidgBfbikI12fK56upQcovcDGer0hc73c6eCOt8zyNPoRq43GvwUGo7dk7U
+                FHfb2/wwti8+6vpKdkTF78lIkmvz/1Wntl+PnjzuK7BcxfdFuOXxKBJkZmtB
+                N3TFA6kLTMUUjTfosUSfRWr0jPorIblGloHPkSCd/gJIrpPHGSKAVPlPZH4b
+                Qr4hmR46z2CDZG4EQJZsEFUOE0Sik78jtI7ly7/j3I//IPnsSeHj55hmz8iZ
+                wJ0RUcXEzCnS3CnSfEw66u0j3I1RZ4exS5iNC92lqKGzFgqXn+Oz8sIfOPdq
+                s+mY98IIF/Nqbe7U7T/FJp0mi0skcY9kgQJf4iusYmqofY3pGJ7A1vC8jW/p
+                rFLWNZpDcReJCkoVlCtYwHVScaOCRTi7YHrwN3dxJtK/5Qj5CF9EMCPYEXIR
+                JiJcijBL/v8ARg7ZkCkGAAA=
+                """,
+        """
+                androidx/lifecycle/ViewModelProvider$Factory.class:
+                H4sIAAAAAAAA/5VSXU8TQRQ9M9tu222VBUH5UESo0iKyQNQHJCTahFhT0EjT
+                xPA03Y516HY32Z1WeOtv8RfoiySamMZHf5Tx7tKg0URlHu7ce+bcc2fu3G/f
+                P30BcB/rDKvCb4WBah07nnot3RPXk05Dybd7QUt6L8Kgr1oyLO4KVwfhSQaM
+                wT4SfeF4wm87z5tH0tUZGAzmtvKV3mEwSuVGAWmYFlLIMKT0GxUxrNUuUugR
+                CbqhFFoyLJTKf80l7vJ2fevvnJ1SuV4n5lItCNvOkdTNUCg/coTvB1poFZC/
+                H+j9nhfrPbzIXYt7gds5IE9mMGbBjh+dbkvd6BYwgUKMXGEYr3UC7Snf2ZNa
+                tIQWVIZ3+wb9A4tNLjZgYB3Cj1Uc0e/w1gZDZTiYtPg0t7g9HFg8a5wFWT49
+                HGzydbbFMk/SX9+Z3ObPpmxjlj9NLWazw4GdWuHrZwdmLLXJkgJ1hvl/NTR3
+                /iaG4v80I4MiQ2bUEYbCOWGto2kGKuQyjNWUL/d73aYM66LpETJRC1zhNUSo
+                4ngE5g5U2xe6Fxefe9nzterKqt9XkaLjxz8/jMpUfV+GFU9EkaTQOgh6oSt3
+                VawyM8ps/JGHDXCazXhxagiNKtkSRU7cHtrTK6fIfkiOy2TNBDSxQrZwRkAO
+                Fu3jyBNiJMkPEjEg/xn2q1OMf8Tk+98ksr9I5EcSdxPOJayOWJdpN3CP7ATh
+                HLdxBzM0QhxLmMVawl6mmwIVYk/RVa4ewqjiWhXTVWLOkou5Kq7jxiFYhHnc
+                PEQ2ghVhIYIZIR/hVoTFCIUISz8ALZc+bgoEAAA=
+                """,
+        """
+                androidx/lifecycle/ViewModelProvider.class:
+                H4sIAAAAAAAA/41QTWsUQRB91bM7k0wmZhM/svnwkyAq4iRBEGIQNBAY2KgY
+                2cueemfapLOz3TDduya3/S3+A0+CB1k8+qOCNXHxnMur916/oqvqz+XPXwBe
+                4iFhS5qisro4T0v9ReUXeanSrlZfj2yhyo+VHetCVRGI0DqTY5mW0pykH/pn
+                KvcRAkK4r432bwjBk6fdBE2EMRqICA1/qh3hcec6H7wmLHcG1pfapEfKy0J6
+                yZ4YjgMelGqYrwEEGrB/rmu1zazYITyaTpJYtEUsWtNJLOZEezrZFdu0R8G7
+                5u9voWiJOrlLdX90KHNvqwvC8+tMtjWLR2gTkv/PLwaeVzxgSljqaKPej4Z9
+                VX2W/ZKdlY7NZdmVla71zEwyY1R1UErnFB8mPrajKleHun5b+zQyXg9VVzvN
+                4bfGWC+9tsZhB4IPOtu9vi/jBqv0SgPNZz8w952JwCZj+M/EXcZkxucRcw1w
+                jzFmb42zq4z3r7rW8YDrK/YXOJv0EGRYzHAjwxJaTLGcYQU3eyCHW7jdQ8Mh
+                drjjEDqs/gWgamwlTAIAAA==
+                """,
+        """
+                androidx/lifecycle/ViewModelStoreOwner.class:
+                H4sIAAAAAAAA/41Ry0rDQBQ9d9qmNlZb3/W5lOrCqLhTBBWEQqug0o2raTLq
+                tOkEkqmPXb/FP3AluJDi0o8Sb6K4dnM4jzvcw53Pr7d3AHtYJaxLE8SRDh69
+                UN8o/8kPldfW6qEVBSq8tFGszh+MiosgQrUr76UXSnPrnXe6yrdF5AjOgTba
+                HhJy9Y12GQU4LvIoEvL2TieEevN/K/YJU81eZENtvJayMpBWsif69zkuSymU
+                UgCBeuw/6lRtMwt2CGujoeuKmnBFldloWBsNd8U2HRc+nh1RFenULqVvy3+r
+                t3qWS54wJVSa2qizQb+j4ivZCdmZbka+DNsy1qn+Nd3LaBD76lSnYvFiYKzu
+                q7ZONKdHxkRWWh2ZBDsQfIPfsulJGGusvEwDhc1XjL0wEVhkdDIzjyXG8s8A
+                SnCzfDnDBaxkP0YY56x8jVwDEw1MNlBBlSmmGpjGzDUowSzmOE/gJphP4HwD
+                iZB0Wu4BAAA=
+                """
+    )
+
+internal val VIEWMODEL_COMPOSE =
+    bytecodeStub(
+        filename = "ViewModel.kt",
+        filepath = "androidx/lifecycle/viewmodel/compose",
+        checksum = 0xa5fbc03d,
+        source =
+            """
+            package androidx.lifecycle.viewmodel.compose
+
+            import androidx.compose.runtime.*
+            import androidx.lifecycle.*
+
+            inline fun <reified VM : ViewModel> viewModel(
+                viewModelStoreOwner: ViewModelStoreOwner = ViewModelStoreOwner(),
+                key: String? = null,
+                factory: ViewModelProvider.Factory? = null,
+                extras: CreationExtras = CreationExtras()
+            ): VM {
+                return factory!!.create()
+            }
+
+            @Composable
+            inline fun <reified VM : ViewModel> viewModel(
+                viewModelStoreOwner: ViewModelStoreOwner = ViewModelStoreOwner(),
+                key: String? = null,
+                noinline initializer: CreationExtras.() -> VM
+            ): VM { return CreationExtras().run { initializer() } }
+        """
+                .trimIndent(),
+        """
+                META-INF/main.kotlin_module:
+                H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijgMuZSScxLKcrPTKnQy8lMS02uTM5J
+                1SvLTC3PzU9JzdFLzs8tyC9OFeIOAwr5goS8S7hkuLiB4nqpFYm5BTmpQry+
+                lUiySgxaDABrUUXUawAAAA==
+                """,
+        """
+                androidx/lifecycle/viewmodel/compose/ViewModelKt.class:
+                H4sIAAAAAAAA/9VWW28bRRT+Zr2215s02bhx67i3pHFzcZP4QgmQGy1pTUzs
+                NNTBUAKUjb1JN7bXlXftNn1AEQ/wxBNPfUXiCQnxgKCAVEXhAQl+Bv8DcWZ9
+                iUOsXECVila758zsmTPfd87Mmfn9r5+fAbiGNYaIauTKJT33KFzQ17XsVrag
+                hau69rBYymmFcLZUfFAytXCGelK8Z9FygzEom2pVDRdUYyN8e21Ty1Kvg8FT
+                bZgxfDOSbOO56Sdtlcra7YeGVp5O7vlKW2Xd2Jg+dORyuVTVc1o5GFez5GSr
+                rfV8WVMtvWTcemSVVXN69FCP0wzfz2RSU4cbzb1QhFYyKYJ9unpwegkSw8V8
+                ySroRnizWgzrhqWVDbUQThgcjalnTTdkBl/2vpbNL5WspUqhsKyW1aJGhgzD
+                I8l/ZrcNpdFMJzpxSkYHuhhcmo1LgsLQ2eqXpmnjjg/24jQf3MswdpLouHGG
+                psvycGgM/SNHpLYTfvTJOIsAQ1dQD64HW9YoSzAcO6sMjry2xeA9GAsG93oN
+                HcPEyXLNMHiMdDP0NGEHc9q6WilYDM9epBWZaJPlo3bd0PHguzFEKZ/RDd2a
+                oyyM8NUzglEZwwgxDByJzY0xGePc9vF/iVjLllqvGFnu3wzH61r0aLbfPuca
+                czi+mfFjJPEqLytzjepyZW9E4xwoVwxLL9I4u62uFTSy6+CZ0dWC/phXn9j+
+                6tMGihvXKJ+6US3laQsPtasPB7s6MYlXOvAyXmU4f1iQ3JiiRUFbXQ2OE9zx
+                lqNrb+8Ho4QyaN3Xzb2+e5XYtYJaXMupXItQcTkq5QxfPMcF9W92VE/DaUqz
+                1JxqqdQnFKsOOu0Z/3j4B1T58lwR6OcjnWvEVshRTP7c2Z6QBUmQBb8g72w3
+                hdLVaDVfqSkDS8rOdkBYYJdlaWdbYX4WEiJCTJIExREQImJMJs0ZEP0s4or1
+                SZLiDpz2il4h4uHfBRaRdr9yCZK8sPu59OtTtrPNm0pHIH4Ct1zvtPVTtkUX
+                WXgWdj+zXSvdu58Kbtkp7T6JRRinSotUyKRoDRwVUPCCX49pa+IvNjpp52h0
+                ppaMxt+VrQd8UwSPU2DdeJvOjnjj7OhsGkzkqb6Hku1O8XSpUs5qN7W1ykZz
+                bprPWVULFdpOX6dTN5blVkfyou1FDqX7G1pcvtof7d9ndNJrIHmI9a+reY0P
+                boczTv/ILJSUo4PRsVhkKkpKbCoWlUO36PYhzpMfhu6kbmhLleKaVl7h5YSH
+                u5RVCxm1rPN2vdOT1jcM1aqUST+VttRsPqU+qP87d6dWlBJGVTd16rphGCXL
+                LmsmxTRh0PabL6imqVFTroUvrvORve1iydBXd5g54A5RCBD5FoKo9MEJF7Xf
+                o9YmSSdJf8jreYrucW8PfR2T4tjdp/D9gHPf8d2Gu/R10YrqppvT+/YF3Em6
+                B+dxwfbqh4SLtnc/LqGfLLk2gMs0lmsKBuHAqu3LrXgQxBWy4fP/QT0ukgmf
+                KH7yBB2/YPjuj7i66BOd1HSypE9020rKJypSzWKcLCaWDgHswAc2YLFbkrpt
+                8CHIdo+bnm6SZwiwn+QlIjFAcpT+R0lebyF4vYVgokkw0SSYaBJM1AjagQ3T
+                7JzYl3ViwzWcY96XOM65Ovop0THpHOsTf8JrAnYwjb04nyEnXeilm58PAZJX
+                6B2il8OaI5cBunvOECwnWXRhliYWSfbZUHlFHG5CHW5CHUaMxgr1DETwYT2f
+                UcDOxuv1bPzG80Ny9sK+ZFyo56I9Fadj0jXW59xHpZYBEUKXuyX+Ck3aQ2AV
+                ouMlogpN3UsXHB9B6qWRvURsj2ikTtRFFjWiTrLfIzrbJDrbJDpbJ+rER9SS
+                qe8s7iBNo+7Z1FfwMcn/R6mBSlBXicJ1onNjFY4E3khgPoGbuJVAHG8msIAE
+                GZh4C4urUEw4TSRNpExIJi6auGRiyUTMxG0TgyaWTYRNDJi4bHJjl70MumiC
+                d+jN2I7e/Rt/dMvZYg8AAA==
+                """
+    )
diff --git a/lifecycle/lifecycle-viewmodel-compose-lint/src/test/java/androidx/lifecycle/viewmodel/compose/lint/ViewModelConstructorInComposableTest.kt b/lifecycle/lifecycle-viewmodel-compose-lint/src/test/java/androidx/lifecycle/viewmodel/compose/lint/ViewModelConstructorInComposableTest.kt
new file mode 100644
index 0000000..5afca98
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel-compose-lint/src/test/java/androidx/lifecycle/viewmodel/compose/lint/ViewModelConstructorInComposableTest.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.lifecycle.viewmodel.compose.lint.lint
+
+import androidx.compose.lint.test.Stubs
+import androidx.lifecycle.lint.ViewModelConstructorInComposableDetector
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ViewModelConstructorInComposableDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = ViewModelConstructorInComposableDetector()
+
+    override fun getIssues(): MutableList<Issue> =
+        mutableListOf(ViewModelConstructorInComposableDetector.ISSUE)
+
+    @Test
+    fun constructInComposableShouldFail() {
+        lint()
+            .files(
+                kotlin(
+                    """
+                    package com.example
+
+                    import androidx.compose.runtime.*
+                    import androidx.lifecycle.ViewModel
+
+                    class MyViewModel: ViewModel()
+                    @Composable
+                    fun Test() {
+                        val viewModel = MyViewModel()
+                        val composableLambda = @Composable {
+                            val vm = MyViewModel()
+                        }
+                    }
+                """
+                ),
+                Stubs.Composable,
+                VIEWMODEL
+            )
+            .run()
+            .expect(
+                """
+src/com/example/MyViewModel.kt:10: Error: Constructing a view model in a composable [ViewModelConstructorInComposable]
+                        val viewModel = MyViewModel()
+                                        ~~~~~~~~~~~
+src/com/example/MyViewModel.kt:12: Error: Constructing a view model in a composable [ViewModelConstructorInComposable]
+                            val vm = MyViewModel()
+                                     ~~~~~~~~~~~
+2 errors, 0 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun useExtensionMethodShouldPass() {
+        lint()
+            .files(
+                kotlin(
+                    """
+                    package com.example
+
+                    import androidx.compose.runtime.*
+                    import androidx.lifecycle.ViewModel
+                    import androidx.lifecycle.viewmodel.compose.viewModel
+
+                    class MyViewModel: ViewModel()
+                    @Composable
+                    fun Test() {
+                        val viewModel = viewModel<MyViewModel>()
+                        val viewModel2 = viewModel { MyViewModel() }
+                    }
+
+                    fun Test2(viewModel: MyViewModel = viewModel<MyViewModel>()) {
+
+                    }
+                """
+                ),
+                Stubs.Composable,
+                VIEWMODEL,
+                VIEWMODEL_COMPOSE
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun constructedOutsideComposableShouldPass() {
+        lint()
+            .files(
+                kotlin(
+                    """
+                    package com.example
+
+                    import androidx.compose.runtime.*
+                    import androidx.lifecycle.ViewModel
+
+                    class MyViewModel: ViewModel()
+                    fun Test() {
+                        val myViewModel = MyViewModel()
+                        val viewModel = ViewModel()
+                    }
+                """
+                ),
+                Stubs.Composable,
+                VIEWMODEL,
+            )
+            .run()
+            .expectClean()
+    }
+}
diff --git a/lifecycle/lifecycle-viewmodel-compose/build.gradle b/lifecycle/lifecycle-viewmodel-compose/build.gradle
index 1167792..22e109f 100644
--- a/lifecycle/lifecycle-viewmodel-compose/build.gradle
+++ b/lifecycle/lifecycle-viewmodel-compose/build.gradle
@@ -89,6 +89,10 @@
     }
 }
 
+dependencies {
+    lintPublish(project(":lifecycle:lifecycle-viewmodel-compose-lint"))
+}
+
 androidx {
     name = "Lifecycle ViewModel Compose"
     type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
diff --git a/settings.gradle b/settings.gradle
index e2ae757..3297f04 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -764,6 +764,7 @@
 includeProject(":lifecycle:lifecycle-viewmodel-compose", [BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-viewmodel-compose:lifecycle-viewmodel-compose-samples", "lifecycle/lifecycle-viewmodel-compose/samples", [BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-viewmodel-compose:integration-tests:lifecycle-viewmodel-demos", [BuildType.COMPOSE])
+includeProject(":lifecycle:lifecycle-viewmodel-compose-lint", [BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-viewmodel-ktx", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-viewmodel-savedstate", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-viewmodel-testing", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE, BuildType.INFRAROGUE, BuildType.KMP])