Adds compose:lint:common module to allow reusing code between Compose lint checks

A lot of Compose lint checks have / will have similar requirements:
- Looking for @Composable annotations on declarations
- Checking to see if something is happening inside a Composable function / lambda body
- Parsing Kotlin metadata from Kotlin class files

This new module collects related functionality to reduce duplication. In the future we can consider publishing this module to allow external developers to build their own Compose-related lint checks.

This CL also:

- Moves internal-lint-checks to compose:lint:internal-lint-checks
- Adds a helper method to BundleInsideHelper to allow bundling dependencies with lint checks, since implementation dependencies aren't supported in lintPublish
- Migrates runtime-lint checks to use compose:lint:common
- Migrates internal-lint-checks to use compose:lint:common

Test: lintDebug
Change-Id: I818cd6564f8d1499a9c0612fa5cbc1fdea483fb8
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXExtension.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXExtension.kt
index 38e09c8..b8a6a40 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXExtension.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXExtension.kt
@@ -126,7 +126,7 @@
                 // add per-project overrides here
                 // for example
                 // the following project is intended to be accessed from Java
-                // ":compose:internal-lint-checks" -> return true
+                // ":compose:lint:internal-lint-checks" -> return true
                 // the following project is not intended to be accessed from Java
                 // ":annotation:annotation" -> return false
             }
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXUiPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXUiPlugin.kt
index 53a49e3..000d567 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXUiPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXUiPlugin.kt
@@ -194,7 +194,7 @@
                 "lintChecks",
                 project.dependencies.project(
                     mapOf(
-                        "path" to ":compose:internal-lint-checks",
+                        "path" to ":compose:lint:internal-lint-checks",
                         "configuration" to "shadow"
                     )
                 )
diff --git a/buildSrc/src/main/kotlin/androidx/build/BundleInsideHelper.kt b/buildSrc/src/main/kotlin/androidx/build/BundleInsideHelper.kt
index 452597f..7d9d5d0 100644
--- a/buildSrc/src/main/kotlin/androidx/build/BundleInsideHelper.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/BundleInsideHelper.kt
@@ -117,6 +117,54 @@
         }
     }
 
+    /**
+     * Creates a configuration for users to use that will be used bundle these dependency
+     * jars inside of this lint check's jar. This is required because lintPublish does
+     * not currently support dependencies, so instead we need to bundle any dependencies with the
+     * lint jar manually. (b/182319899)
+     *
+     * ```
+     * dependencies {
+     *     if (rootProject.hasProperty("android.injected.invoked.from.ide")) {
+     *         compileOnly(LINT_API_LATEST)
+     *     } else {
+     *         compileOnly(LINT_API_MIN)
+     *     }
+     *     compileOnly(KOTLIN_STDLIB)
+     *     // Include this library inside the resulting lint jar
+     *     bundleInside(project(":foo-lint-utils"))
+     * }
+     * ```
+     * @receiver the project that should bundle jars specified by these configurations
+     */
+    @JvmStatic
+    fun Project.forInsideLintJar() {
+        val bundle = configurations.create("bundleInside")
+        val compileOnly = configurations.getByName("compileOnly")
+        val testImplementation = configurations.getByName("testImplementation")
+        // bundleInside dependencies should be included as compileOnly as well
+        compileOnly.setExtendsFrom(listOf(bundle))
+        testImplementation.setExtendsFrom(listOf(bundle))
+
+        tasks.named("jar").configure { jarTask ->
+            jarTask as Jar
+            jarTask.dependsOn(bundle)
+            jarTask.from({
+                bundle
+                    // The stdlib is already bundled with lint, so no need to include it manually
+                    // in the lint.jar if any dependencies here depend on it
+                    .filter { !it.name.contains("kotlin-stdlib") }
+                    .map { file ->
+                        if (file.isDirectory) {
+                            file
+                        } else {
+                            zipTree(file)
+                        }
+                    }
+            })
+        }
+    }
+
     private fun Project.configureRepackageTaskForType(
         type: String,
         from: String,
diff --git a/buildSrc/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt b/buildSrc/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt
index 44c28d7..a299124 100644
--- a/buildSrc/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt
@@ -130,6 +130,12 @@
     if (name.startsWith("lint")) return false
     if (name == "metalava") return false
 
+    // Don't check any configurations that directly bundle the dependencies with the output
+    if (name == "bundleInside") return false
+
+    // Don't check any compile-only configurations
+    if (name.startsWith("compile")) return false
+
     return true
 }
 
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisibilityDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisibilityDemo.kt
index 3e39440..d4f9186 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisibilityDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisibilityDemo.kt
@@ -164,10 +164,9 @@
             targetWidth = { fullWidth -> fullWidth / 10 },
             // Overwrites the default animation with tween for this shrink animation.
             animationSpec = tween(durationMillis = 400)
-        ) + fadeOut()
-    ) {
-        content()
-    }
+        ) + fadeOut(),
+        content = content
+    )
 }
 
 @OptIn(ExperimentalAnimationApi::class)
@@ -188,10 +187,9 @@
             // Overwrites the ending position of the slide-out to 200 (pixels) to the right
             targetOffsetX = { 200 },
             animationSpec = spring(stiffness = Spring.StiffnessHigh)
-        ) + fadeOut()
-    ) {
-        content()
-    }
+        ) + fadeOut(),
+        content = content
+    )
 }
 
 @OptIn(ExperimentalAnimationApi::class)
@@ -206,10 +204,9 @@
         exit = fadeOut(
             // Overwrites the default animation with tween
             animationSpec = tween(durationMillis = 250)
-        )
-    ) {
-        content()
-    }
+        ),
+        content = content
+    )
 }
 
 @OptIn(ExperimentalAnimationApi::class)
@@ -224,8 +221,7 @@
         ) + expandVertically(
             expandFrom = Alignment.Top
         ) + fadeIn(initialAlpha = 0.3f),
-        exit = slideOutVertically() + shrinkVertically() + fadeOut()
-    ) {
-        content()
-    }
+        exit = slideOutVertically() + shrinkVertically() + fadeOut(),
+        content = content
+    )
 }
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/resources/Resources.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/resources/Resources.kt
index 75e7d481..62430ab 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/resources/Resources.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/resources/Resources.kt
@@ -149,6 +149,7 @@
     Icon(Icons.Rounded.Menu, contentDescription = "Localized description")
 }
 
+@Suppress("UnnecessaryLambdaCreation")
 private object ResourcesSnippet9 {
     // Define and load the fonts of the app
     private val light = Font(R.font.raleway_light, FontWeight.W300)
diff --git a/compose/internal-lint-checks/src/main/java/androidx/compose/lint/ListIteratorDetector.kt b/compose/internal-lint-checks/src/main/java/androidx/compose/lint/ListIteratorDetector.kt
deleted file mode 100644
index bc8ff03..0000000
--- a/compose/internal-lint-checks/src/main/java/androidx/compose/lint/ListIteratorDetector.kt
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright 2021 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.compose.lint
-
-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 com.intellij.lang.jvm.annotation.JvmAnnotationArrayValue
-import com.intellij.lang.jvm.annotation.JvmAnnotationAttributeValue
-import com.intellij.lang.jvm.annotation.JvmAnnotationConstantValue
-import com.intellij.psi.PsiAnnotation
-import com.intellij.psi.PsiMethod
-import com.intellij.psi.impl.compiled.ClsMethodImpl
-import com.intellij.psi.util.ClassUtil
-import com.intellij.psi.util.InheritanceUtil
-import kotlinx.metadata.KmClassifier
-import kotlinx.metadata.KmDeclarationContainer
-import kotlinx.metadata.KmFunction
-import kotlinx.metadata.jvm.KotlinClassHeader
-import kotlinx.metadata.jvm.KotlinClassMetadata
-import kotlinx.metadata.jvm.signature
-import org.jetbrains.kotlin.psi.KtForExpression
-import org.jetbrains.kotlin.psi.KtNamedFunction
-import org.jetbrains.uast.UCallExpression
-import org.jetbrains.uast.UForEachExpression
-import org.jetbrains.uast.UTypeReferenceExpression
-import org.jetbrains.uast.resolveToUElement
-import org.jetbrains.uast.toUElement
-
-/**
- * Lint [Detector] to prevent allocating Iterators when iterating on a [List]. Instead of using
- * `for (e in list)` or `list.forEach {}`, more efficient iteration methods should be used, such as
- * `for (i in list.indices) { list[i]... }` or `list.fastForEach`.
- */
-class ListIteratorDetector : Detector(), SourceCodeScanner {
-    override fun getApplicableUastTypes() = listOf(
-        UForEachExpression::class.java,
-        UCallExpression::class.java
-    )
-
-    override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
-        override fun visitForEachExpression(node: UForEachExpression) {
-            // Type of the variable we are iterating on, i.e the type of `b` in `for (a in b)`
-            val iteratedValueType = node.iteratedValue.getExpressionType()
-            // We are iterating on a List
-            if (InheritanceUtil.isInheritor(iteratedValueType, JavaListFqn)) {
-                // Find the `in` keyword to use as location
-                val inKeyword = (node.sourcePsi as? KtForExpression)?.inKeyword
-                val location = if (inKeyword == null) {
-                    context.getNameLocation(node)
-                } else {
-                    context.getNameLocation(inKeyword)
-                }
-                context.report(
-                    ISSUE,
-                    node,
-                    location,
-                    "Creating an unnecessary Iterator to iterate through a List"
-                )
-            }
-        }
-
-        override fun visitCallExpression(node: UCallExpression) {
-            val receiverType = node.receiverType
-
-            // We are calling a method on a `List` type
-            if (receiverType != null &&
-                InheritanceUtil.isInheritor(node.receiverType, JavaListFqn)
-            ) {
-                when (val method = node.resolveToUElement()?.sourcePsi) {
-                    // Parsing a class file
-                    is ClsMethodImpl -> {
-                        method.checkForIterableReceiver(node)
-                    }
-                    // Parsing Kotlin source
-                    is KtNamedFunction -> {
-                        method.checkForIterableReceiver(node)
-                    }
-                }
-            }
-        }
-
-        private fun ClsMethodImpl.checkForIterableReceiver(node: UCallExpression) {
-            val classKotlinMetadataAnnotation = containingClass?.annotations?.find {
-                it.hasQualifiedName(KotlinMetadataFqn)
-            } ?: return
-
-            val metadata = KotlinClassMetadata.read(classKotlinMetadataAnnotation.toHeader())
-                ?: return
-
-            // Since we are visiting a function, the null branches shouldn't be called
-            val kmPackage: KmDeclarationContainer = when (metadata) {
-                is KotlinClassMetadata.Class -> metadata.toKmClass()
-                is KotlinClassMetadata.FileFacade -> metadata.toKmPackage()
-                is KotlinClassMetadata.SyntheticClass -> null
-                is KotlinClassMetadata.MultiFileClassFacade -> null
-                is KotlinClassMetadata.MultiFileClassPart -> metadata.toKmPackage()
-                is KotlinClassMetadata.Unknown -> null
-            }!!
-
-            val kmFunction = kmPackage.findKmFunctionForPsiMethod(this)
-
-            kmFunction?.let {
-                if (it.hasIterableReceiver) {
-                    context.report(
-                        ISSUE,
-                        node,
-                        context.getNameLocation(node),
-                        "Creating an unnecessary Iterator to iterate through a List"
-                    )
-                }
-            }
-        }
-
-        private fun KtNamedFunction.checkForIterableReceiver(node: UCallExpression) {
-            val receiver = receiverTypeReference
-            // If there is no receiver, or the receiver isn't an Iterable, ignore
-            if ((receiver.toUElement() as? UTypeReferenceExpression)
-                ?.getQualifiedName() != JavaIterableFQN
-            ) return
-
-            context.report(
-                ISSUE,
-                node,
-                context.getNameLocation(node),
-                "Creating an unnecessary Iterator to iterate through a List"
-            )
-        }
-    }
-
-    companion object {
-        val ISSUE = Issue.create(
-            "ListIterator",
-            "Creating an unnecessary Iterator to iterate through a List",
-            "Iterable<T> extension methods and using `for (a in list)` will create an " +
-                "Iterator object - in hot code paths this can cause a lot of extra allocations " +
-                "which is something we want to avoid. Instead, use a method that doesn't " +
-                "allocate, such as `fastForEach`, or use `for (a in list.indices)` as iterating " +
-                "through an `IntRange` does not allocate an Iterator, and becomes just a simple " +
-                "for loop.",
-            Category.PERFORMANCE, 5, Severity.ERROR,
-            Implementation(
-                ListIteratorDetector::class.java,
-                Scope.JAVA_FILE_SCOPE
-            )
-        )
-    }
-}
-
-/**
- * Returns a [KotlinClassHeader] by parsing the attributes of this @kotlin.Metadata annotation.
- *
- * See: https://github.com/udalov/kotlinx-metadata-examples/blob/master/src/main/java
- * /examples/FindKotlinGeneratedMethods.java
- */
-private fun PsiAnnotation.toHeader(): KotlinClassHeader {
-    val attributes = attributes.associate { it.attributeName to it.attributeValue }
-
-    fun JvmAnnotationAttributeValue.parseString(): String =
-        (this as JvmAnnotationConstantValue).constantValue as String
-
-    fun JvmAnnotationAttributeValue.parseInt(): Int =
-        (this as JvmAnnotationConstantValue).constantValue as Int
-
-    fun JvmAnnotationAttributeValue.parseStringArray(): Array<String> =
-        (this as JvmAnnotationArrayValue).values.map {
-            it.parseString()
-        }.toTypedArray()
-
-    fun JvmAnnotationAttributeValue.parseIntArray(): IntArray =
-        (this as JvmAnnotationArrayValue).values.map {
-            it.parseInt()
-        }.toTypedArray().toIntArray()
-
-    val kind = attributes["k"]?.parseInt()
-    val metadataVersion = attributes["mv"]?.parseIntArray()
-    val bytecodeVersion = attributes["bv"]?.parseIntArray()
-    val data1 = attributes["d1"]?.parseStringArray()
-    val data2 = attributes["d2"]?.parseStringArray()
-    val extraString = attributes["xs"]?.parseString()
-    val packageName = attributes["pn"]?.parseString()
-    val extraInt = attributes["xi"]?.parseInt()
-
-    return KotlinClassHeader(
-        kind,
-        metadataVersion,
-        bytecodeVersion,
-        data1,
-        data2,
-        extraString,
-        packageName,
-        extraInt
-    )
-}
-
-/**
- * @return the corresponding [KmFunction] in [this] for the given [method], matching by name and
- * signature.
- */
-private fun KmDeclarationContainer.findKmFunctionForPsiMethod(method: PsiMethod): KmFunction? {
-    val expectedName = method.name
-    val expectedSignature = ClassUtil.getAsmMethodSignature(method)
-
-    return functions.find {
-        it.name == expectedName && it.signature?.desc == expectedSignature
-    }
-}
-
-/**
- * @return true if this function is an extension function on Iterable
- */
-private val KmFunction.hasIterableReceiver: Boolean
-    get() = receiverParameterType?.classifier == IterableClassifier
-
-private const val KotlinMetadataFqn = "kotlin.Metadata"
-// Kotlin collections on JVM are just the underlying Java collections
-private const val JavaListFqn = "java.util.List"
-private const val JavaIterableFQN = "java.lang.Iterable"
-private val IterableClassifier = KmClassifier.Class("kotlin/collections/Iterable")
diff --git a/compose/internal-lint-checks/build.gradle b/compose/lint/common/build.gradle
similarity index 69%
copy from compose/internal-lint-checks/build.gradle
copy to compose/lint/common/build.gradle
index b06cfd5..feb7618 100644
--- a/compose/internal-lint-checks/build.gradle
+++ b/compose/lint/common/build.gradle
@@ -18,25 +18,20 @@
 
 import static androidx.build.dependencies.DependenciesKt.*
 
-buildscript {
-    dependencies {
-        classpath "com.github.jengelman.gradle.plugins:shadow:4.0.4"
-    }
-}
-
 plugins {
     id("AndroidXPlugin")
     id("kotlin")
 }
 
-apply(plugin:"com.github.johnrengelman.shadow")
-
 dependencies {
-    compileOnly(LINT_API_LATEST)
+    if (rootProject.hasProperty("android.injected.invoked.from.ide")) {
+        compileOnly(LINT_API_LATEST)
+    } else {
+        compileOnly(LINT_API_MIN)
+    }
     compileOnly(KOTLIN_STDLIB)
-    api(project(":lint-checks"))
 
-    implementation(KOTLIN_METADATA_JVM)
+    api(KOTLIN_METADATA_JVM)
 
     testImplementation(KOTLIN_STDLIB)
     testImplementation(LINT_CORE)
@@ -44,10 +39,8 @@
 }
 
 androidx {
-    name = "Compose internal lint checks"
+    name = "Compose Lint Utils"
     type = LibraryType.LINT
-    inceptionYear = "2019"
-    description = "Internal lint checks for Compose"
+    inceptionYear = "2021"
+    description = "Lint utils used for writing Compose related lint checks"
 }
-
-tasks["shadowJar"].archiveFileName = "merged.jar"
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt b/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt
new file mode 100644
index 0000000..7334704
--- /dev/null
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2021 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.compose.lint
+
+import com.intellij.psi.impl.compiled.ClsMethodImpl
+import com.intellij.psi.impl.compiled.ClsParameterImpl
+import kotlinx.metadata.jvm.annotations
+import org.jetbrains.kotlin.psi.KtAnnotated
+import org.jetbrains.kotlin.psi.KtFunction
+import org.jetbrains.kotlin.psi.KtTypeReference
+import org.jetbrains.uast.UAnnotation
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.ULambdaExpression
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UParameter
+import org.jetbrains.uast.UTypeReferenceExpression
+import org.jetbrains.uast.UVariable
+import org.jetbrains.uast.getContainingDeclaration
+import org.jetbrains.uast.getContainingUMethod
+import org.jetbrains.uast.getParameterForArgument
+import org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression
+import org.jetbrains.uast.toUElement
+import org.jetbrains.uast.withContainingElements
+
+/**
+ * Returns whether this [UCallExpression] is invoked within the body of a Composable function or
+ * lambda.
+ *
+ * This searches parent declarations until we find a lambda expression or a function, and looks
+ * to see if these are Composable. Additionally, if we are inside a non-Composable lambda, the
+ * lambda is a parameter on an inline function, and the inline function is within a Composable
+ * lambda / function, this will also return true - since scoping functions / iterator functions
+ * are commonly used within Composables.
+ */
+fun UCallExpression.isInvokedWithinComposable(): Boolean {
+    // The nearest property / function / etc declaration that contains this call expression
+    val containingDeclaration = getContainingDeclaration()
+
+    // Look through containing elements until we find a lambda or a method
+    for (element in withContainingElements) {
+        when (element) {
+            is ULambdaExpression -> {
+                if (element.isComposable) {
+                    return true
+                }
+                val parent = element.uastParent
+                if (parent is KotlinUFunctionCallExpression && parent.isDeclarationInline) {
+                    // We are now in a non-composable lambda parameter inside an inline function
+                    // For example, a scoping function such as run {} or apply {} - since the
+                    // body will be inlined and this is a common case, try to see if there is
+                    // a parent composable function above us, since it is still most likely
+                    // an error to call these methods inside an inline function, inside a
+                    // Composable function.
+                    continue
+                } else {
+                    return false
+                }
+            }
+            is UMethod -> {
+                return element.isComposable
+            }
+            // Stop when we reach the parent declaration to avoid escaping the scope. This
+            // shouldn't be called unless there is a UAST type we don't handle above.
+            containingDeclaration -> return false
+        }
+    }
+    return false
+}
+
+// TODO: https://youtrack.jetbrains.com/issue/KT-45406
+// KotlinUMethodWithFakeLightDelegate.hasAnnotation() (for reified functions for example)
+// doesn't find annotations, so just look at the annotations directly.
+// Note: annotations is deprecated but the replacement uAnnotations isn't available on the
+// version of lint / uast we compile against, shouldn't be an issue when the above issue is fixed.
+/**
+ * Returns whether this method is @Composable or not
+ */
+@Suppress("DEPRECATION")
+val UMethod.isComposable
+    get() = annotations.any { it.qualifiedName == Names.Runtime.Composable.javaFqn }
+
+/**
+ * Returns whether this variable's type is @Composable or not
+ */
+val UVariable.isComposable: Boolean
+    get() {
+        // Annotation on the lambda
+        val annotationOnLambda = when (val initializer = uastInitializer) {
+            is ULambdaExpression -> {
+                val source = initializer.sourcePsi
+                if (source is KtFunction) {
+                    // Anonymous function, val foo = @Composable fun() {}
+                    source.hasComposableAnnotation
+                } else {
+                    // Lambda, val foo = @Composable {}
+                    initializer.findAnnotation(Names.Runtime.Composable.javaFqn) != null
+                }
+            }
+            else -> false
+        }
+        // Annotation on the type, foo: @Composable () -> Unit = { }
+        val annotationOnType = typeReference?.isComposable == true
+        return annotationOnLambda || annotationOnType
+    }
+
+/**
+ * Returns whether this parameter's type is @Composable or not
+ */
+val UParameter.isComposable: Boolean
+    get() = when (sourcePsi) {
+        // The parameter is in a class file. Currently type annotations aren't currently added to
+        // the underlying type (https://youtrack.jetbrains.com/issue/KT-45307), so instead we use
+        // the metadata annotation.
+        is ClsParameterImpl -> {
+            // Find the containing method, so we can get metadata from the containing class
+            val containingMethod = getContainingUMethod()!!.sourcePsi as ClsMethodImpl
+            val kmFunction = containingMethod.toKmFunction()
+
+            val kmValueParameter = kmFunction?.valueParameters?.find {
+                it.name == name
+            }
+
+            kmValueParameter?.type?.annotations?.find {
+                it.className == Names.Runtime.Composable.kmClassName
+            } != null
+        }
+        // The parameter is in a source declaration
+        else -> typeReference!!.isComposable
+    }
+
+/**
+ * Returns whether this lambda expression is @Composable or not
+ */
+val ULambdaExpression.isComposable: Boolean
+    get() = when (val lambdaParent = uastParent) {
+        // Function call with a lambda parameter
+        is UCallExpression -> {
+            val parameter = lambdaParent.getParameterForArgument(this)
+            (parameter.toUElement() as? UParameter)?.isComposable == true
+        }
+        // A local / non-local lambda variable
+        is UVariable -> {
+            lambdaParent.isComposable
+        }
+        // Either a new UAST type we haven't handled, or non-Kotlin declarations
+        else -> false
+    }
+
+/**
+ * Returns whether this type reference is @Composable or not
+ */
+private val UTypeReferenceExpression.isComposable: Boolean
+    get() {
+        if (type.hasAnnotation(Names.Runtime.Composable.javaFqn)) return true
+
+        // Annotations should be available on the PsiType itself in 1.4.30+, but we are
+        // currently on an older version of UAST / Kotlin embedded compiled
+        // (https://youtrack.jetbrains.com/issue/KT-45244), so we need to manually check the
+        // underlying type reference. Until then, the above check will always fail.
+        return (sourcePsi as? KtTypeReference)?.hasComposableAnnotation == true
+    }
+
+/**
+ * Returns whether this annotated declaration has a Composable annotation
+ */
+private val KtAnnotated.hasComposableAnnotation: Boolean
+    get() = annotationEntries.any {
+        (it.toUElement() as UAnnotation).qualifiedName == Names.Runtime.Composable.javaFqn
+    }
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/KotlinMetadataUtils.kt b/compose/lint/common/src/main/java/androidx/compose/lint/KotlinMetadataUtils.kt
new file mode 100644
index 0000000..347b842
--- /dev/null
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/KotlinMetadataUtils.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2021 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.compose.lint
+
+import com.intellij.lang.jvm.annotation.JvmAnnotationArrayValue
+import com.intellij.lang.jvm.annotation.JvmAnnotationAttributeValue
+import com.intellij.lang.jvm.annotation.JvmAnnotationConstantValue
+import com.intellij.psi.PsiAnnotation
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.impl.compiled.ClsMemberImpl
+import com.intellij.psi.impl.compiled.ClsMethodImpl
+import com.intellij.psi.util.ClassUtil
+import kotlinx.metadata.KmDeclarationContainer
+import kotlinx.metadata.KmFunction
+import kotlinx.metadata.jvm.KotlinClassHeader
+import kotlinx.metadata.jvm.KotlinClassMetadata
+import kotlinx.metadata.jvm.signature
+
+/**
+ * @return the corresponding [KmFunction] for this [ClsMethodImpl], or `null` if there is no
+ * corresponding [KmFunction].
+ */
+fun ClsMethodImpl.toKmFunction(): KmFunction? =
+    getKmDeclarationContainer()?.findKmFunctionForPsiMethod(this)
+
+// TODO: https://youtrack.jetbrains.com/issue/KT-45310
+// Currently there is no built in support for parsing kotlin metadata from kotlin class files, so
+// we need to manually inspect the annotations and work with Cls* (compiled PSI).
+/**
+ * Returns the [KmDeclarationContainer] using the kotlin.Metadata annotation present on the
+ * surrounding class. Returns null if there is no surrounding annotation (not parsing a Kotlin
+ * class file), the annotation data is for an unsupported version of Kotlin, or if the metadata
+ * represents a synthetic class.
+ */
+private fun ClsMemberImpl<*>.getKmDeclarationContainer(): KmDeclarationContainer? {
+    val classKotlinMetadataAnnotation = containingClass?.annotations?.find {
+        // hasQualifiedName() not available on the min version of Lint we compile against
+        it.qualifiedName == KotlinMetadataFqn
+    } ?: return null
+
+    val metadata = KotlinClassMetadata.read(classKotlinMetadataAnnotation.toHeader())
+        ?: return null
+
+    return when (metadata) {
+        is KotlinClassMetadata.Class -> metadata.toKmClass()
+        is KotlinClassMetadata.FileFacade -> metadata.toKmPackage()
+        is KotlinClassMetadata.SyntheticClass -> null
+        is KotlinClassMetadata.MultiFileClassFacade -> null
+        is KotlinClassMetadata.MultiFileClassPart -> metadata.toKmPackage()
+        is KotlinClassMetadata.Unknown -> null
+    }
+}
+
+/**
+ * Returns a [KotlinClassHeader] by parsing the attributes of this @kotlin.Metadata annotation.
+ *
+ * See: https://github.com/udalov/kotlinx-metadata-examples/blob/master/src/main/java
+ * /examples/FindKotlinGeneratedMethods.java
+ */
+private fun PsiAnnotation.toHeader(): KotlinClassHeader {
+    val attributes = attributes.associate { it.attributeName to it.attributeValue }
+
+    fun JvmAnnotationAttributeValue.parseString(): String =
+        (this as JvmAnnotationConstantValue).constantValue as String
+
+    fun JvmAnnotationAttributeValue.parseInt(): Int =
+        (this as JvmAnnotationConstantValue).constantValue as Int
+
+    fun JvmAnnotationAttributeValue.parseStringArray(): Array<String> =
+        (this as JvmAnnotationArrayValue).values.map {
+            it.parseString()
+        }.toTypedArray()
+
+    fun JvmAnnotationAttributeValue.parseIntArray(): IntArray =
+        (this as JvmAnnotationArrayValue).values.map {
+            it.parseInt()
+        }.toTypedArray().toIntArray()
+
+    val kind = attributes["k"]?.parseInt()
+    val metadataVersion = attributes["mv"]?.parseIntArray()
+    val bytecodeVersion = attributes["bv"]?.parseIntArray()
+    val data1 = attributes["d1"]?.parseStringArray()
+    val data2 = attributes["d2"]?.parseStringArray()
+    val extraString = attributes["xs"]?.parseString()
+    val packageName = attributes["pn"]?.parseString()
+    val extraInt = attributes["xi"]?.parseInt()
+
+    return KotlinClassHeader(
+        kind,
+        metadataVersion,
+        bytecodeVersion,
+        data1,
+        data2,
+        extraString,
+        packageName,
+        extraInt
+    )
+}
+
+/**
+ * @return the corresponding [KmFunction] in [this] for the given [method], matching by name and
+ * signature.
+ */
+private fun KmDeclarationContainer.findKmFunctionForPsiMethod(method: PsiMethod): KmFunction? {
+    val expectedName = method.name
+    val expectedSignature = ClassUtil.getAsmMethodSignature(method)
+
+    return functions.find {
+        it.name == expectedName && it.signature?.desc == expectedSignature
+    }
+}
+
+private const val KotlinMetadataFqn = "kotlin.Metadata"
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/KotlinUtils.kt b/compose/lint/common/src/main/java/androidx/compose/lint/KotlinUtils.kt
new file mode 100644
index 0000000..4b20849
--- /dev/null
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/KotlinUtils.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2021 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.compose.lint
+
+import com.intellij.psi.impl.compiled.ClsMethodImpl
+import kotlinx.metadata.Flag
+import org.jetbrains.kotlin.lexer.KtTokens.INLINE_KEYWORD
+import org.jetbrains.kotlin.psi.KtFunction
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.resolveToUElement
+
+/**
+ * @return whether the resolved declaration for this call expression is an inline function
+ */
+val UCallExpression.isDeclarationInline: Boolean
+    get() {
+        return when (val source = resolveToUElement()?.sourcePsi) {
+            // Parsing a method defined in a class file
+            is ClsMethodImpl -> {
+                val flags = source.toKmFunction()?.flags ?: return false
+                return Flag.Function.IS_INLINE(flags)
+            }
+            // Parsing a method defined in Kotlin source
+            is KtFunction -> {
+                source.hasModifier(INLINE_KEYWORD)
+            }
+            // Parsing another declaration (such as a property) which cannot be inline, or
+            // a non-Kotlin declaration
+            else -> false
+        }
+    }
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt b/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt
new file mode 100644
index 0000000..5ec6d27
--- /dev/null
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2021 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.compose.lint
+
+import kotlinx.metadata.ClassName
+
+/**
+ * Contains common names used for lint checks.
+ */
+object Names {
+    object Runtime {
+        val packageName = Package("androidx.compose.runtime")
+
+        val Composable = Name(packageName, "Composable")
+        val CompositionLocal = Name(packageName, "CompositionLocal")
+        val Remember = Name(packageName, "remember")
+    }
+}
+
+/**
+ * Represents a qualified package
+ *
+ * @property segments the segments representing the package
+ */
+class PackageName internal constructor(internal val segments: List<String>) {
+    /**
+     * The Java-style package name for this [Name], separated with `.`
+     */
+    val javaPackageName: String
+        get() = segments.joinToString(".")
+}
+
+/**
+ * Represents the qualified name for an element
+ *
+ * @property pkg the package for this element
+ * @property nameSegments the segments representing the element - there can be multiple in the
+ * case of nested classes.
+ */
+class Name internal constructor(
+    private val pkg: PackageName,
+    private val nameSegments: List<String>
+) {
+    /**
+     * The short name for this [Name]
+     */
+    val shortName: String
+        get() = nameSegments.last()
+
+    /**
+     * The Java-style fully qualified name for this [Name], separated with `.`
+     */
+    val javaFqn: String
+        get() = pkg.segments.joinToString(".", postfix = ".") +
+            nameSegments.joinToString(".")
+
+    /**
+     * The [ClassName] for use with kotlinx.metadata. Note that in kotlinx.metadata the actual
+     * type might be different from the underlying JVM type, for example:
+     * kotlin/Int -> java/lang/Integer
+     */
+    val kmClassName: ClassName
+        get() = pkg.segments.joinToString("/", postfix = "/") +
+            nameSegments.joinToString(".")
+}
+
+/**
+ * @return a [PackageName] with a Java-style (separated with `.`) [packageName].
+ */
+fun Package(packageName: String): PackageName =
+    PackageName(packageName.split("."))
+
+/**
+ * @return a [Name] with the provided [pkg] and Java-style (separated with `.`) [shortName].
+ */
+fun Name(pkg: PackageName, shortName: String): Name =
+    Name(pkg, shortName.split("."))
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/PsiUtils.kt b/compose/lint/common/src/main/java/androidx/compose/lint/PsiUtils.kt
new file mode 100644
index 0000000..ccb3a74
--- /dev/null
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/PsiUtils.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2021 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.compose.lint
+
+import com.intellij.psi.PsiJavaFile
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiType
+
+/**
+ * Returns whether [this] has [packageName] as its package name.
+ */
+fun PsiMethod.isInPackageName(packageName: PackageName): Boolean {
+    val actual = (containingFile as? PsiJavaFile)?.packageName
+    return packageName.javaPackageName == actual
+}
+
+/**
+ * Whether this [PsiMethod] returns Unit
+ */
+val PsiMethod.returnsUnit
+    get() = returnType == PsiType.VOID
diff --git a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/Stubs.kt b/compose/lint/common/src/main/java/androidx/compose/lint/Stubs.kt
similarity index 60%
rename from compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/Stubs.kt
rename to compose/lint/common/src/main/java/androidx/compose/lint/Stubs.kt
index 83372e1..e2d3c46 100644
--- a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/Stubs.kt
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/Stubs.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Android Open Source Project
+ * Copyright 2021 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.
@@ -14,14 +14,16 @@
  * limitations under the License.
  */
 
-@file:Suppress("UnstableApiUsage")
+package androidx.compose.lint
 
-package androidx.compose.runtime.lint
+import org.intellij.lang.annotations.Language
 
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-
-val composableStub: LintDetectorTest.TestFile = LintDetectorTest.kotlin(
-    """
+/**
+ * Common Compose-related lint stubs used for testing
+ */
+object Stubs {
+    val Composable = stub(
+        """
         package androidx.compose.runtime
 
         @MustBeDocumented
@@ -33,11 +35,36 @@
             AnnotationTarget.PROPERTY
         )
         annotation class Composable
-    """
-)
+        """
+    )
 
-val rememberStub: LintDetectorTest.TestFile = LintDetectorTest.kotlin(
-"""
+    val Modifier = stub(
+        """
+        package androidx.compose.ui
+
+        import androidx.compose.ui.platform.InspectorInfo
+        import androidx.compose.ui.platform.InspectorValueInfo
+
+        interface Modifier {
+          infix fun then(other: Modifier): Modifier =
+              if (other === Modifier) this else CombinedModifier(this, other)
+
+          interface Element : Modifier
+
+          companion object : Modifier {
+            override infix fun then(other: Modifier): Modifier = other
+          }
+        }
+
+        class CombinedModifier(
+            private val outer: Modifier,
+            private val inner: Modifier
+        ) : Modifier
+        """
+    )
+
+    val Remember = stub(
+        """
         package androidx.compose.runtime
 
         import androidx.compose.runtime.Composable
@@ -71,21 +98,9 @@
             vararg inputs: Any?,
             calculation: () -> V
         ): V = calculation()
-    """
-)
+        """
+    )
+}
 
-val coroutineBuildersStub: LintDetectorTest.TestFile = LintDetectorTest.kotlin(
-    """
-        package kotlinx.coroutines
-
-        object CoroutineScope
-
-        fun CoroutineScope.async(
-            block: suspend CoroutineScope.() -> Unit
-        ) {}
-
-        fun CoroutineScope.launch(
-            block: suspend CoroutineScope.() -> Unit
-        ) {}
-    """
-)
+// @Language isn't available as a type annotation, so we need a parameter
+private fun stub(@Language("kotlin") code: String) = code
\ No newline at end of file
diff --git a/compose/internal-lint-checks/build.gradle b/compose/lint/internal-lint-checks/build.gradle
similarity index 93%
rename from compose/internal-lint-checks/build.gradle
rename to compose/lint/internal-lint-checks/build.gradle
index b06cfd5..83bcf45 100644
--- a/compose/internal-lint-checks/build.gradle
+++ b/compose/lint/internal-lint-checks/build.gradle
@@ -35,8 +35,7 @@
     compileOnly(LINT_API_LATEST)
     compileOnly(KOTLIN_STDLIB)
     api(project(":lint-checks"))
-
-    implementation(KOTLIN_METADATA_JVM)
+    implementation(project(":compose:lint:common"))
 
     testImplementation(KOTLIN_STDLIB)
     testImplementation(LINT_CORE)
@@ -44,7 +43,7 @@
 }
 
 androidx {
-    name = "Compose internal lint checks"
+    name = "Compose Internal Lint Checks"
     type = LibraryType.LINT
     inceptionYear = "2019"
     description = "Internal lint checks for Compose"
diff --git a/compose/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
similarity index 100%
rename from compose/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
rename to compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ListIteratorDetector.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ListIteratorDetector.kt
new file mode 100644
index 0000000..7e22dda
--- /dev/null
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ListIteratorDetector.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2021 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.compose.lint
+
+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 com.intellij.psi.impl.compiled.ClsMethodImpl
+import com.intellij.psi.util.InheritanceUtil
+import kotlinx.metadata.KmClassifier
+import org.jetbrains.kotlin.psi.KtForExpression
+import org.jetbrains.kotlin.psi.KtNamedFunction
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UForEachExpression
+import org.jetbrains.uast.UTypeReferenceExpression
+import org.jetbrains.uast.resolveToUElement
+import org.jetbrains.uast.toUElement
+
+/**
+ * Lint [Detector] to prevent allocating Iterators when iterating on a [List]. Instead of using
+ * `for (e in list)` or `list.forEach {}`, more efficient iteration methods should be used, such as
+ * `for (i in list.indices) { list[i]... }` or `list.fastForEach`.
+ */
+class ListIteratorDetector : Detector(), SourceCodeScanner {
+    override fun getApplicableUastTypes() = listOf(
+        UForEachExpression::class.java,
+        UCallExpression::class.java
+    )
+
+    override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
+        override fun visitForEachExpression(node: UForEachExpression) {
+            // Type of the variable we are iterating on, i.e the type of `b` in `for (a in b)`
+            val iteratedValueType = node.iteratedValue.getExpressionType()
+            // We are iterating on a List
+            if (InheritanceUtil.isInheritor(iteratedValueType, JavaList.javaFqn)) {
+                // Find the `in` keyword to use as location
+                val inKeyword = (node.sourcePsi as? KtForExpression)?.inKeyword
+                val location = if (inKeyword == null) {
+                    context.getNameLocation(node)
+                } else {
+                    context.getNameLocation(inKeyword)
+                }
+                context.report(
+                    ISSUE,
+                    node,
+                    location,
+                    "Creating an unnecessary Iterator to iterate through a List"
+                )
+            }
+        }
+
+        override fun visitCallExpression(node: UCallExpression) {
+            val receiverType = node.receiverType
+
+            // We are calling a method on a `List` type
+            if (receiverType != null &&
+                InheritanceUtil.isInheritor(node.receiverType, JavaList.javaFqn)
+            ) {
+                when (val method = node.resolveToUElement()?.sourcePsi) {
+                    // Parsing a class file
+                    is ClsMethodImpl -> {
+                        method.checkForIterableReceiver(node)
+                    }
+                    // Parsing Kotlin source
+                    is KtNamedFunction -> {
+                        method.checkForIterableReceiver(node)
+                    }
+                }
+            }
+        }
+
+        private fun ClsMethodImpl.checkForIterableReceiver(node: UCallExpression) {
+            val kmFunction = this.toKmFunction()
+
+            kmFunction?.let {
+                if (it.receiverParameterType?.classifier == KotlinIterableClassifier) {
+                    context.report(
+                        ISSUE,
+                        node,
+                        context.getNameLocation(node),
+                        "Creating an unnecessary Iterator to iterate through a List"
+                    )
+                }
+            }
+        }
+
+        private fun KtNamedFunction.checkForIterableReceiver(node: UCallExpression) {
+            val receiver = receiverTypeReference
+            // If there is no receiver, or the receiver isn't an Iterable, ignore
+            if ((receiver.toUElement() as? UTypeReferenceExpression)
+                ?.getQualifiedName() != JavaIterable.javaFqn
+            ) return
+
+            context.report(
+                ISSUE,
+                node,
+                context.getNameLocation(node),
+                "Creating an unnecessary Iterator to iterate through a List"
+            )
+        }
+    }
+
+    companion object {
+        val ISSUE = Issue.create(
+            "ListIterator",
+            "Creating an unnecessary Iterator to iterate through a List",
+            "Iterable<T> extension methods and using `for (a in list)` will create an " +
+                "Iterator object - in hot code paths this can cause a lot of extra allocations " +
+                "which is something we want to avoid. Instead, use a method that doesn't " +
+                "allocate, such as `fastForEach`, or use `for (a in list.indices)` as iterating " +
+                "through an `IntRange` does not allocate an Iterator, and becomes just a simple " +
+                "for loop.",
+            Category.PERFORMANCE, 5, Severity.ERROR,
+            Implementation(
+                ListIteratorDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            )
+        )
+    }
+}
+
+// Kotlin collections on JVM are just the underlying Java collections
+private val JavaLangPackageName = Package("java.lang")
+private val JavaUtilPackageName = Package("java.util")
+private val JavaList = Name(JavaUtilPackageName, "List")
+private val JavaIterable = Name(JavaLangPackageName, "Iterable")
+
+private val KotlinCollectionsPackageName = Package("kotlin.collections")
+private val KotlinIterable = Name(KotlinCollectionsPackageName, "Iterable")
+private val KotlinIterableClassifier = KmClassifier.Class(KotlinIterable.kmClassName)
\ No newline at end of file
diff --git a/compose/internal-lint-checks/src/main/java/androidx/compose/lint/ModifierInspectorInfoDetector.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ModifierInspectorInfoDetector.kt
similarity index 100%
rename from compose/internal-lint-checks/src/main/java/androidx/compose/lint/ModifierInspectorInfoDetector.kt
rename to compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ModifierInspectorInfoDetector.kt
diff --git a/compose/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt
similarity index 66%
rename from compose/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt
rename to compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt
index 2cde8e4..2f4a022 100644
--- a/compose/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt
@@ -27,20 +27,14 @@
 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.UastLintUtils.Companion.tryResolveUDeclaration
 import com.intellij.psi.impl.source.PsiClassReferenceType
-import org.jetbrains.kotlin.psi.KtCallExpression
-import org.jetbrains.kotlin.psi.KtCallableDeclaration
-import org.jetbrains.kotlin.psi.KtTypeReference
-import org.jetbrains.kotlin.psi.psiUtil.referenceExpression
-import org.jetbrains.uast.UAnnotation
 import org.jetbrains.uast.ULambdaExpression
-import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UVariable
 import org.jetbrains.uast.kotlin.KotlinUBlockExpression
 import org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression
 import org.jetbrains.uast.kotlin.KotlinUImplicitReturnExpression
-import org.jetbrains.uast.resolveToUElement
 import org.jetbrains.uast.toUElement
-import org.jetbrains.uast.tryResolve
 
 /**
  * Lint [Detector] to ensure that we are not creating extra lambdas just to emit already captured
@@ -129,57 +123,18 @@
             // shouldn't matter that much in practice.
             if (functionType.reference.canonicalText.contains(NonExistentClass)) return
 
-            // Component nodes are classes that are invoked as if they are a function call, but
-            // they aren't actually a function call and so they cannot be inlined. Unfortunately
-            // since this is done in a compiler plugin, when running lint we don't have a way to
-            // understand this better, so we just check to see if the name looks like it is a node.
-            if (parentExpression.isLayoutNodeInvocation) return
-
-            // Find the index of the corresponding parameter in the source declaration, that
-            // matches this lambda expression's invocation
-            val parameterIndex = parentExpression.valueArguments.indexOf(node)
-
-            // Parent expression source declaration
-            val parentDeclaration = parentExpression.resolveToUElement() as? UMethod ?: return
-
-            val expectedComposable = when (val source = parentDeclaration.sourcePsi) {
-                // The source is in Kotlin source, so check the parameter for @Composable
-                is KtCallableDeclaration -> {
-                    // Currently type annotations don't appear on the psiType in the version of
-                    // UAST / PSI we are using, so we have to look through the type reference.
-                    // Should be fixed when Lint upgrades the version to 1.4.30+.
-                    val typeReference = source.valueParameters[parameterIndex]!!
-                        .typeReference as KtTypeReference
-                    typeReference.annotationEntries.any {
-                        (it.toUElement() as UAnnotation).qualifiedName == ComposableFqn
-                    }
-                }
-                // If we cannot resolve the parent expression as a KtCallableDeclaration, then
-                // the source is Java source, or in a class file. We ignore Java source, and
-                // currently there is no way to see the @Composable annotation in the class file
-                // (https://youtrack.jetbrains.com/issue/KT-45307). Instead we can look for the
-                // presence of a `Composer` parameter, as this is added by the Compose compiler
-                // to every Composable function / lambda.
-                else -> {
-                    parentDeclaration.uastParameters[parameterIndex].type.canonicalText
-                        .contains(ComposerFqn)
-                }
-            }
+            val expectedComposable = node.isComposable
 
             // Hack to get the psi of the lambda declaration / source. The !!s here probably
             // aren't safe, but nothing fails with them currently - so it could be a useful
             // indicator if something breaks in the future to let us know to update this lint check.
-            val resolvedLambda = expression.sourcePsi.calleeExpression!!.toUElement()!!.tryResolve()
-                .toUElement()!!.sourcePsi!!
+            val resolvedLambdaSource = expression.sourcePsi.calleeExpression!!.toUElement()!!
+                .tryResolveUDeclaration()!!.sourcePsi!!.toUElement()
 
-            // Unfortunately as Composability isn't carried through UAST, and there are many types
-            // of declarations (types such as foo: @Composable () -> Unit, properties such as val
-            // foo = @Composable {}) the best way to cover this is just check if we contain this
-            // annotation in text. Definitely not ideal, but it should cover most cases so it is
-            // the simplest way for now. Note in particular this will return true for (rare) cases
-            // like (@Composable () -> Unit) -> Unit, so this might need to be updated in the
-            // future if this becomes a common problem.
-            val isComposable = resolvedLambda.text.contains("@Composable")
+            val isComposable = when (resolvedLambdaSource) {
+                is UVariable -> resolvedLambdaSource.isComposable
+                else -> throw IllegalStateException(resolvedLambdaSource.toString())
+            }
 
             if (isComposable != expectedComposable) return
 
@@ -193,10 +148,6 @@
     }
 
     companion object {
-        private val KotlinUFunctionCallExpression.isLayoutNodeInvocation
-            get() = (sourcePsi as? KtCallExpression)?.referenceExpression()?.text
-                ?.endsWith("Node") == true
-
         private const val NonExistentClass = "error.NonExistentClass"
 
         private const val explanation =
@@ -217,6 +168,3 @@
         )
     }
 }
-
-private const val ComposableFqn = "androidx.compose.runtime.Composable"
-private const val ComposerFqn = "androidx.compose.runtime.Composer"
diff --git a/compose/internal-lint-checks/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry b/compose/lint/internal-lint-checks/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
similarity index 100%
rename from compose/internal-lint-checks/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
rename to compose/lint/internal-lint-checks/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
diff --git a/compose/internal-lint-checks/src/test/java/androidx/compose/lint/ListIteratorDetectorTest.kt b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ListIteratorDetectorTest.kt
similarity index 100%
rename from compose/internal-lint-checks/src/test/java/androidx/compose/lint/ListIteratorDetectorTest.kt
rename to compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ListIteratorDetectorTest.kt
diff --git a/compose/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt
similarity index 93%
rename from compose/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt
rename to compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt
index 5e95ed8..23e2a69 100644
--- a/compose/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt
+++ b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt
@@ -32,7 +32,7 @@
 
     override fun getIssues(): List<Issue> = listOf(ModifierInspectorInfoDetector.ISSUE)
 
-    private val inspectableInfoFile = kotlin(
+    private val inspectableInfoStub = kotlin(
         """
         package androidx.compose.ui.platform
 
@@ -98,30 +98,13 @@
         """
     ).indented()
 
-    private val modifierFile = kotlin(
+    private val composedStub = kotlin(
         """
         package androidx.compose.ui
 
         import androidx.compose.ui.platform.InspectorInfo
         import androidx.compose.ui.platform.InspectorValueInfo
 
-        interface Modifier {
-          infix fun then(other: Modifier): Modifier =
-              if (other === Modifier) this else CombinedModifier(this, other)
-
-          interface Element : Modifier {
-          }
-
-          companion object : Modifier {
-            override infix fun then(other: Modifier): Modifier = other
-          }
-        }
-
-        class CombinedModifier(
-            private val outer: Modifier,
-            private val inner: Modifier
-        ) : Modifier {}
-
         fun Modifier.composed(
             inspectorInfo: InspectorInfo.() -> Unit = NoInspectorInfo,
             factory: Modifier.() -> Modifier
@@ -131,26 +114,15 @@
             inspectorInfo: InspectorInfo.() -> Unit,
             val factory: Modifier.() -> Modifier
         ) : Modifier.Element, InspectorValueInfo(inspectorInfo)
-
-        """
-    ).indented()
-
-    private val rememberFile = kotlin(
-        """
-        package androidx.compose.runtime
-
-        fun <T> remember(calculation: () -> T): T = calculation()
-
-        class Remember
-
         """
     ).indented()
 
     @Test
     fun existingInspectorInfo() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -183,8 +155,9 @@
     @Test
     fun existingInspectorInfoWithStatementsBeforeDefinition() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -232,8 +205,9 @@
     @Test
     fun existingInspectorInfoWithValue() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -265,8 +239,9 @@
     @Test
     fun existingInspectorInfoViaSynonym() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -308,8 +283,9 @@
     @Test
     fun existingInspectorInfoWithAnonymousClass() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -331,8 +307,9 @@
     @Test
     fun existingInspectorInfoWithDataClassMemberValues() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -398,8 +375,9 @@
     @Test
     fun existingInspectorInfoWithConditional() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -447,8 +425,9 @@
     @Test
     fun existingInspectorInfoWithWhen() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -487,8 +466,9 @@
     @Test
     fun existingInspectorInfoWithConditionals() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -545,8 +525,9 @@
     @Test
     fun composedModifierWithInspectorInfo() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -585,9 +566,10 @@
     @Test
     fun rememberModifierInfo() {
         lint().files(
-            modifierFile,
-            rememberFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            kotlin(Stubs.Remember),
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -622,7 +604,8 @@
     @Test
     fun emptyModifier() {
         lint().files(
-            modifierFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -641,8 +624,9 @@
     @Test
     fun acceptMissingInspectorInfoInSamples() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui.demos.whatever
@@ -668,8 +652,9 @@
     @Test
     fun missingInspectorInfo() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -702,8 +687,9 @@
     @Test
     fun composedModifierWithMissingInspectorInfo() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -736,8 +722,9 @@
     @Test
     fun missingInspectorInfoFromInnerClassImplementation() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -775,8 +762,9 @@
     @Test
     fun inspectorInfoWithWrongName() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -815,8 +803,9 @@
     @Test
     fun inspectorInfoWithWrongValue() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -855,8 +844,9 @@
     @Test
     fun inspectorInfoWithWrongValueWhenMultipleAreAvailable() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -895,8 +885,9 @@
     @Test
     fun inspectorInfoWithWrongParameterNameInProperties() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -936,8 +927,9 @@
     @Test
     fun inspectorInfoWithMismatchInProperties() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -976,8 +968,9 @@
     @Test
     fun inspectorInfoWithMissingDebugSelector() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -1017,8 +1010,9 @@
     @Test
     fun inspectorInfoWithMissingName() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -1056,8 +1050,9 @@
     @Test
     fun inspectorInfoWithMissingVariables() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -1102,8 +1097,9 @@
     @Test
     fun inspectorInfoWithMissingDataClassMemberValues() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -1147,8 +1143,9 @@
     @Test
     fun missingInfoInConditionals() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
diff --git a/compose/internal-lint-checks/src/test/java/androidx/compose/lint/UnnecessaryLambdaCreationDetectorTest.kt b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/UnnecessaryLambdaCreationDetectorTest.kt
similarity index 91%
rename from compose/internal-lint-checks/src/test/java/androidx/compose/lint/UnnecessaryLambdaCreationDetectorTest.kt
rename to compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/UnnecessaryLambdaCreationDetectorTest.kt
index 12a1f87..bbe37b1 100644
--- a/compose/internal-lint-checks/src/test/java/androidx/compose/lint/UnnecessaryLambdaCreationDetectorTest.kt
+++ b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/UnnecessaryLambdaCreationDetectorTest.kt
@@ -25,28 +25,13 @@
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 import androidx.compose.lint.UnnecessaryLambdaCreationDetector.Companion.ISSUE
+import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
 import org.intellij.lang.annotations.Language
 
 /* ktlint-disable max-line-length */
 @RunWith(JUnit4::class)
 class UnnecessaryLambdaCreationDetectorTest {
 
-    private val composableStub = kt(
-        """
-        package androidx.compose.runtime
-
-        @MustBeDocumented
-        @Retention(AnnotationRetention.BINARY)
-        @Target(
-            AnnotationTarget.FUNCTION,
-            AnnotationTarget.TYPE,
-            AnnotationTarget.TYPE_PARAMETER,
-            AnnotationTarget.PROPERTY
-        )
-        annotation class Composable
-    """
-    )
-
     private val stub = kt(
         """
         package test
@@ -68,7 +53,7 @@
 
     private fun check(@Language("kotlin") code: String): TestLintResult {
         return TestLintTask.lint()
-            .files(kt(code.trimIndent()), stub, composableStub)
+            .files(kt(code.trimIndent()), stub, kotlin(Stubs.Composable))
             .allowMissingSdk(true)
             .issues(ISSUE)
             .run()
@@ -223,14 +208,7 @@
                 }
             }
         """
-        ).expect(
-            """
-src/test/test.kt:23: Error: Creating an unnecessary lambda to emit a captured lambda [UnnecessaryLambdaCreation]
-        parameterizedLambda(child)
-        ~~~~~~~~~~~~~~~~~~~
-1 errors, 0 warnings
-        """
-        )
+        ).expectClean()
     }
 
     @Test
diff --git a/compose/runtime/runtime-lint/build.gradle b/compose/runtime/runtime-lint/build.gradle
index 751acb3..684899c 100644
--- a/compose/runtime/runtime-lint/build.gradle
+++ b/compose/runtime/runtime-lint/build.gradle
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+import androidx.build.BundleInsideHelper
 import androidx.build.LibraryGroups
 import androidx.build.LibraryType
 
@@ -24,27 +25,7 @@
     id("kotlin")
 }
 
-// New configuration that allows us to specify a dependency we will include into the resulting
-// jar, since dependencies aren't currently allowed in lint projects included via lintPublish
-// (b/182319899)
-configurations {
-    bundleWithJar
-    testImplementation.extendsFrom bundleWithJar
-    compileOnly.extendsFrom bundleWithJar
-}
-
-jar {
-    dependsOn configurations.bundleWithJar
-    from {
-        configurations.bundleWithJar
-                // The stdlib is already bundled with lint, so no need to include it manually in
-                // the lint.jar
-                .filter( { !(it.name =~ /kotlin-stdlib.*\.jar/ )})
-                .collect {
-                    it.isDirectory() ? it : zipTree(it)
-                }
-    }
-}
+BundleInsideHelper.forInsideLintJar(project)
 
 dependencies {
     // compileOnly because we use lintChecks and it doesn't allow other types of deps
@@ -55,7 +36,7 @@
         compileOnly(LINT_API_MIN)
     }
     compileOnly(KOTLIN_STDLIB)
-    bundleWithJar(KOTLIN_METADATA_JVM)
+    bundleInside(project(":compose:lint:common"))
 
     testImplementation(KOTLIN_STDLIB)
     testImplementation(LINT_CORE)
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableCoroutineCreationDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableCoroutineCreationDetector.kt
index 2617bdf..28e6b4b 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableCoroutineCreationDetector.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableCoroutineCreationDetector.kt
@@ -18,6 +18,10 @@
 
 package androidx.compose.runtime.lint
 
+import androidx.compose.lint.Name
+import androidx.compose.lint.Package
+import androidx.compose.lint.isInPackageName
+import androidx.compose.lint.isInvokedWithinComposable
 import com.android.tools.lint.detector.api.Category
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Implementation
@@ -26,13 +30,8 @@
 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.PsiJavaFile
 import com.intellij.psi.PsiMethod
 import org.jetbrains.uast.UCallExpression
-import org.jetbrains.uast.UElement
-import org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression
-import org.jetbrains.uast.kotlin.KotlinULambdaExpression
-import org.jetbrains.uast.kotlin.declarations.KotlinUMethod
 import java.util.EnumSet
 
 /**
@@ -40,68 +39,19 @@
  * body of a composable function / lambda.
  */
 class ComposableCoroutineCreationDetector : Detector(), SourceCodeScanner {
-    override fun getApplicableMethodNames() = listOf(AsyncShortName, LaunchShortName)
+    override fun getApplicableMethodNames() = listOf(Async.shortName, Launch.shortName)
 
     override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
-        val packageName = (method.containingFile as? PsiJavaFile)?.packageName
-        if (packageName != CoroutinePackageName) return
-        val name = method.name
+        if (!method.isInPackageName(CoroutinePackageName)) return
 
-        var expression: UElement? = node
-
-        // Limit the search depth in case of an error - in most cases the depth should be
-        // fairly shallow unless there are many if / else / while statements.
-        var depth = 0
-
-        // Find the parent function / lambda this call expression is inside
-        while (depth < 10) {
-            expression = expression?.uastParent
-
-            // TODO: this won't handle inline functions, but we also don't know if they are
-            // inline when they are defined in bytecode because this information doesn't
-            // exist in PSI. If this information isn't added to PSI / UAST, we would need to
-            // manually parse the @Metadata annotation.
-            when (expression) {
-                // In the body of a lambda
-                is KotlinULambdaExpression -> {
-                    if (expression.isComposable) {
-                        context.report(
-                            CoroutineCreationDuringComposition,
-                            node,
-                            context.getNameLocation(node),
-                            "Calls to $name should happen inside a LaunchedEffect and " +
-                                "not composition"
-                        )
-                        return
-                    }
-                    val parent = expression.uastParent
-                    if (parent is KotlinUFunctionCallExpression && parent.isDeclarationInline) {
-                        // We are now in a non-composable lambda parameter inside an inline function
-                        // For example, a scoping function such as run {} or apply {} - since the
-                        // body will be inlined and this is a common case, try to see if there is
-                        // a parent composable function above us, since it is still most likely
-                        // an error to call these methods inside an inline function, inside a
-                        // Composable function.
-                        continue
-                    } else {
-                        return
-                    }
-                }
-                // In the body of a function
-                is KotlinUMethod -> {
-                    if (expression.hasAnnotation("androidx.compose.runtime.Composable")) {
-                        context.report(
-                            CoroutineCreationDuringComposition,
-                            node,
-                            context.getNameLocation(node),
-                            "Calls to $name should happen inside a LaunchedEffect and " +
-                                "not composition"
-                        )
-                    }
-                    return
-                }
-            }
-            depth++
+        if (node.isInvokedWithinComposable()) {
+            context.report(
+                CoroutineCreationDuringComposition,
+                node,
+                context.getNameLocation(node),
+                "Calls to ${method.name} should happen inside a LaunchedEffect and " +
+                    "not composition"
+            )
         }
     }
 
@@ -125,3 +75,7 @@
         )
     }
 }
+
+private val CoroutinePackageName = Package("kotlinx.coroutines")
+private val Async = Name(CoroutinePackageName, "async")
+private val Launch = Name(CoroutinePackageName, "launch")
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetector.kt
index 85272a1..ad32a95 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetector.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetector.kt
@@ -18,6 +18,8 @@
 
 package androidx.compose.runtime.lint
 
+import androidx.compose.lint.isComposable
+import androidx.compose.lint.returnsUnit
 import com.android.tools.lint.client.api.UElementHandler
 import com.android.tools.lint.detector.api.Category
 import com.android.tools.lint.detector.api.Detector
@@ -28,15 +30,12 @@
 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.PsiType
 import org.jetbrains.kotlin.psi.KtFunctionType
 import org.jetbrains.kotlin.psi.KtNullableType
 import org.jetbrains.kotlin.psi.KtParameter
-import org.jetbrains.uast.UAnnotation
 import org.jetbrains.uast.UElement
 import org.jetbrains.uast.UMethod
 import org.jetbrains.uast.UParameter
-import org.jetbrains.uast.toUElement
 import java.util.EnumSet
 
 /**
@@ -56,7 +55,7 @@
             if (!node.isComposable) return
 
             // Ignore non-unit composable functions
-            if (node.returnType != PsiType.VOID) return
+            if (!node.returnsUnit) return
 
             /**
              * Small class to hold information from lambda properties needed for lint checks.
@@ -72,22 +71,15 @@
                 // an extension function - just ignore it.
                 val ktParameter = parameter.sourcePsi as? KtParameter ?: return@mapNotNull null
 
-                val typeReference = ktParameter.typeReference!!
+                val isComposable = parameter.isComposable
 
-                // Currently type annotations don't appear on the psiType in the version of
-                // UAST / PSI we are using, so we have to look through the type reference.
-                // Should be fixed when Lint upgrades the version to 1.4.30+.
-                val hasComposableAnnotationOnType = typeReference.annotationEntries.any {
-                    (it.toUElement() as UAnnotation).qualifiedName == ComposableFqn
-                }
-
-                val functionType = when (val typeElement = typeReference.typeElement) {
-                    is KtFunctionType -> typeElement
-                    is KtNullableType -> typeElement.innerType as? KtFunctionType
+                val functionType = when (val type = ktParameter.typeReference!!.typeElement) {
+                    is KtFunctionType -> type
+                    is KtNullableType -> type.innerType as? KtFunctionType
                     else -> null
                 }
 
-                if (functionType != null && hasComposableAnnotationOnType) {
+                if (functionType != null && isComposable) {
                     ComposableLambdaParameterInfo(parameter, functionType)
                 } else {
                     null
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableNamingDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableNamingDetector.kt
index e4e905b..b45d87b 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableNamingDetector.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableNamingDetector.kt
@@ -18,6 +18,8 @@
 
 package androidx.compose.runtime.lint
 
+import androidx.compose.lint.isComposable
+import androidx.compose.lint.returnsUnit
 import com.android.tools.lint.client.api.UElementHandler
 import com.android.tools.lint.detector.api.Category
 import com.android.tools.lint.detector.api.Detector
@@ -28,7 +30,6 @@
 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.PsiType
 import org.jetbrains.uast.UMethod
 import java.util.EnumSet
 import java.util.Locale
@@ -107,5 +108,3 @@
         )
     }
 }
-
-private val UMethod.returnsUnit get() = returnType == PsiType.VOID
\ No newline at end of file
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/CompositionLocalNamingDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/CompositionLocalNamingDetector.kt
index f9250bc..74d6f2c 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/CompositionLocalNamingDetector.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/CompositionLocalNamingDetector.kt
@@ -18,6 +18,7 @@
 
 package androidx.compose.runtime.lint
 
+import androidx.compose.lint.Names
 import com.android.tools.lint.client.api.UElementHandler
 import com.android.tools.lint.detector.api.Category
 import com.android.tools.lint.detector.api.Detector
@@ -53,10 +54,10 @@
             if ((node.sourcePsi as? KtProperty)?.isLocal == true) return
 
             val type = node.type
-            if (!InheritanceUtil.isInheritor(type, CompositionLocalFqn)) return
+            if (!InheritanceUtil.isInheritor(type, Names.Runtime.CompositionLocal.javaFqn)) return
 
             val name = node.name
-            if (name!!.startsWith(CompositionLocalPrefix, ignoreCase = true)) return
+            if (name!!.startsWith("Local", ignoreCase = true)) return
 
             // Kotlinc can't disambiguate overloads for report / getNameLocation otherwise
             val uElementNode: UElement = node
@@ -86,6 +87,3 @@
         )
     }
 }
-
-private const val CompositionLocalFqn = "androidx.compose.runtime.CompositionLocal"
-private const val CompositionLocalPrefix = "Local"
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RememberDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RememberDetector.kt
index 74ff784e..cfe0589 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RememberDetector.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RememberDetector.kt
@@ -18,6 +18,8 @@
 
 package androidx.compose.runtime.lint
 
+import androidx.compose.lint.Names
+import androidx.compose.lint.isInPackageName
 import com.android.tools.lint.detector.api.Category
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Implementation
@@ -26,7 +28,6 @@
 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.PsiJavaFile
 import com.intellij.psi.PsiMethod
 import com.intellij.psi.PsiType
 import org.jetbrains.uast.UCallExpression
@@ -36,10 +37,10 @@
  * [Detector] that checks `remember` calls to make sure they are not returning [Unit].
  */
 class RememberDetector : Detector(), SourceCodeScanner {
-    override fun getApplicableMethodNames(): List<String> = listOf(RememberShortName)
+    override fun getApplicableMethodNames(): List<String> = listOf(Names.Runtime.Remember.shortName)
 
     override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
-        if ((method.containingFile as? PsiJavaFile)?.packageName == RuntimePackageName) {
+        if (method.isInPackageName(Names.Runtime.packageName)) {
             if (node.getExpressionType() == PsiType.VOID) {
                 context.report(
                     RememberReturnType,
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/Utils.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/Utils.kt
deleted file mode 100644
index 6d78e36..0000000
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/Utils.kt
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright 2020 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.compose.runtime.lint
-
-import com.intellij.lang.jvm.annotation.JvmAnnotationArrayValue
-import com.intellij.lang.jvm.annotation.JvmAnnotationAttributeValue
-import com.intellij.lang.jvm.annotation.JvmAnnotationConstantValue
-import com.intellij.psi.PsiAnnotation
-import com.intellij.psi.PsiMethod
-import com.intellij.psi.impl.compiled.ClsMemberImpl
-import com.intellij.psi.impl.compiled.ClsMethodImpl
-import com.intellij.psi.impl.compiled.ClsParameterImpl
-import com.intellij.psi.util.ClassUtil
-import kotlinx.metadata.Flag
-import kotlinx.metadata.KmDeclarationContainer
-import kotlinx.metadata.KmFunction
-import kotlinx.metadata.jvm.KotlinClassHeader
-import kotlinx.metadata.jvm.KotlinClassMetadata
-import kotlinx.metadata.jvm.annotations
-import kotlinx.metadata.jvm.signature
-import org.jetbrains.kotlin.lexer.KtTokens.INLINE_KEYWORD
-import org.jetbrains.kotlin.psi.KtNamedFunction
-import org.jetbrains.kotlin.psi.KtParameter
-import org.jetbrains.kotlin.psi.KtTypeReference
-import org.jetbrains.uast.UAnnotation
-import org.jetbrains.uast.ULambdaExpression
-import org.jetbrains.uast.UMethod
-import org.jetbrains.uast.UParameter
-import org.jetbrains.uast.getContainingUMethod
-import org.jetbrains.uast.getParameterForArgument
-import org.jetbrains.uast.kotlin.AbstractKotlinUVariable
-import org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression
-import org.jetbrains.uast.resolveToUElement
-import org.jetbrains.uast.toUElement
-
-// TODO: KotlinUMethodWithFakeLightDelegate.hasAnnotation() returns null for some reason, so just
-// look at the annotations directly
-// TODO: annotations is deprecated but the replacement uAnnotations isn't available on the
-// version of lint / uast we compile against
-/**
- * Returns whether this method is @Composable or not
- */
-@Suppress("DEPRECATION")
-val UMethod.isComposable
-    get() = annotations.any { it.qualifiedName == ComposableFqn }
-
-/**
- * Returns whether this parameter's type is @Composable or not
- */
-val UParameter.isComposable: Boolean
-    get() = when (val source = sourcePsi) {
-        // The parameter is defined in Kotlin source
-        is KtParameter -> source.typeReference!!.isComposable
-        // The parameter is in a class file. Currently type annotations aren't added to the
-        // underlying type (https://youtrack.jetbrains.com/issue/KT-45307), so instead we use the
-        // metadata annotation.
-        is ClsParameterImpl -> {
-            // Find the containing method, so we can get metadata from the containing class
-            val containingMethod = getContainingUMethod()!!.sourcePsi as ClsMethodImpl
-            val declarationContainer = containingMethod.getKmDeclarationContainer()
-            val kmFunction = declarationContainer?.findKmFunctionForPsiMethod(containingMethod)
-
-            val kmValueParameter = kmFunction?.valueParameters?.find {
-                it.name == name
-            }
-
-            kmValueParameter?.type?.annotations?.find {
-                it.className == KmComposableFqn
-            } != null
-        }
-        // The parameter is in Java source / other, ignore
-        else -> false
-    }
-
-/**
- * Returns whether this type reference is @Composable or not
- */
-val KtTypeReference.isComposable: Boolean
-    // This annotation should be available on the PsiType itself in 1.4.30+, but we are
-    // currently on an older version of UAST / Kotlin embedded compiled
-    // (https://youtrack.jetbrains.com/issue/KT-45244)
-    get() = annotationEntries.any {
-        (it.toUElement() as UAnnotation).qualifiedName == ComposableFqn
-    }
-
-/**
- * Returns whether this lambda expression is @Composable or not
- */
-val ULambdaExpression.isComposable: Boolean
-    get() {
-        when (val lambdaParent = uastParent) {
-            // Function call with a lambda parameter
-            is KotlinUFunctionCallExpression -> {
-                val parameter = lambdaParent.getParameterForArgument(this) ?: return false
-                if (!(parameter.toUElement() as UParameter).isComposable) return false
-            }
-            // A local / non-local lambda variable
-            is AbstractKotlinUVariable -> {
-                val hasComposableAnnotationOnLambda = findAnnotation(ComposableFqn) != null
-                val hasComposableAnnotationOnType =
-                    (lambdaParent.typeReference?.sourcePsi as? KtTypeReference)
-                        ?.isComposable == true
-
-                if (!hasComposableAnnotationOnLambda && !hasComposableAnnotationOnType) return false
-            }
-            // This probably shouldn't be called, but safe return in case a new UAST type is added
-            // in the future
-            else -> return false
-        }
-        return true
-    }
-
-/**
- * @return whether the resolved declaration for this call expression is an inline function
- */
-val KotlinUFunctionCallExpression.isDeclarationInline: Boolean
-    get() {
-        return when (val source = resolveToUElement()?.sourcePsi) {
-            // Parsing a method defined in a class file
-            is ClsMethodImpl -> {
-                val declarationContainer = source.getKmDeclarationContainer()
-
-                val flags = declarationContainer
-                    ?.findKmFunctionForPsiMethod(source)?.flags ?: return false
-                return Flag.Function.IS_INLINE(flags)
-            }
-            // Parsing a method defined in Kotlin source
-            is KtNamedFunction -> {
-                source.hasModifier(INLINE_KEYWORD)
-            }
-            // Parsing something else (such as a property) which cannot be inline
-            else -> false
-        }
-    }
-
-// TODO: https://youtrack.jetbrains.com/issue/KT-45310
-// Currently there is no built in support for parsing kotlin metadata from kotlin class files, so
-// we need to manually inspect the annotations and work with Cls* (compiled PSI).
-/**
- * Returns the [KmDeclarationContainer] using the kotlin.Metadata annotation present on the
- * surrounding class. Returns null if there is no surrounding annotation (not parsing a Kotlin
- * class file), the annotation data is for an unsupported version of Kotlin, or if the metadata
- * represents a synthetic
- */
-private fun ClsMemberImpl<*>.getKmDeclarationContainer(): KmDeclarationContainer? {
-    val classKotlinMetadataAnnotation = containingClass?.annotations?.find {
-        // hasQualifiedName() not available on the min version of Lint we compile against
-        it.qualifiedName == KotlinMetadataFqn
-    } ?: return null
-
-    val metadata = KotlinClassMetadata.read(classKotlinMetadataAnnotation.toHeader())
-        ?: return null
-
-    return when (metadata) {
-        is KotlinClassMetadata.Class -> metadata.toKmClass()
-        is KotlinClassMetadata.FileFacade -> metadata.toKmPackage()
-        is KotlinClassMetadata.SyntheticClass -> null
-        is KotlinClassMetadata.MultiFileClassFacade -> null
-        is KotlinClassMetadata.MultiFileClassPart -> metadata.toKmPackage()
-        is KotlinClassMetadata.Unknown -> null
-    }
-}
-
-/**
- * Returns a [KotlinClassHeader] by parsing the attributes of this @kotlin.Metadata annotation.
- *
- * See: https://github.com/udalov/kotlinx-metadata-examples/blob/master/src/main/java
- * /examples/FindKotlinGeneratedMethods.java
- */
-private fun PsiAnnotation.toHeader(): KotlinClassHeader {
-    val attributes = attributes.associate { it.attributeName to it.attributeValue }
-
-    fun JvmAnnotationAttributeValue.parseString(): String =
-        (this as JvmAnnotationConstantValue).constantValue as String
-
-    fun JvmAnnotationAttributeValue.parseInt(): Int =
-        (this as JvmAnnotationConstantValue).constantValue as Int
-
-    fun JvmAnnotationAttributeValue.parseStringArray(): Array<String> =
-        (this as JvmAnnotationArrayValue).values.map {
-            it.parseString()
-        }.toTypedArray()
-
-    fun JvmAnnotationAttributeValue.parseIntArray(): IntArray =
-        (this as JvmAnnotationArrayValue).values.map {
-            it.parseInt()
-        }.toTypedArray().toIntArray()
-
-    val kind = attributes["k"]?.parseInt()
-    val metadataVersion = attributes["mv"]?.parseIntArray()
-    val bytecodeVersion = attributes["bv"]?.parseIntArray()
-    val data1 = attributes["d1"]?.parseStringArray()
-    val data2 = attributes["d2"]?.parseStringArray()
-    val extraString = attributes["xs"]?.parseString()
-    val packageName = attributes["pn"]?.parseString()
-    val extraInt = attributes["xi"]?.parseInt()
-
-    return KotlinClassHeader(
-        kind,
-        metadataVersion,
-        bytecodeVersion,
-        data1,
-        data2,
-        extraString,
-        packageName,
-        extraInt
-    )
-}
-
-/**
- * @return the corresponding [KmFunction] in [this] for the given [method], matching by name and
- * signature.
- */
-private fun KmDeclarationContainer.findKmFunctionForPsiMethod(method: PsiMethod): KmFunction? {
-    val expectedName = method.name
-    val expectedSignature = ClassUtil.getAsmMethodSignature(method)
-
-    return functions.find {
-        it.name == expectedName && it.signature?.desc == expectedSignature
-    }
-}
-
-const val RuntimePackageName = "androidx.compose.runtime"
-
-const val ComposableFqn = "$RuntimePackageName.Composable"
-// kotlinx.metadata represents separators as `/` instead of `.`
-val KmComposableFqn get() = ComposableFqn.replace(".", "/")
-
-const val RememberShortName = "remember"
-
-const val CoroutinePackageName = "kotlinx.coroutines"
-const val AsyncShortName = "async"
-const val LaunchShortName = "launch"
-
-private const val KotlinMetadataFqn = "kotlin.Metadata"
diff --git a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableCoroutineCreationDetectorTest.kt b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableCoroutineCreationDetectorTest.kt
index bd2f0bb..c3a1f89 100644
--- a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableCoroutineCreationDetectorTest.kt
+++ b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableCoroutineCreationDetectorTest.kt
@@ -18,6 +18,7 @@
 
 package androidx.compose.runtime.lint
 
+import androidx.compose.lint.Stubs
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
@@ -40,6 +41,22 @@
     override fun getIssues(): MutableList<Issue> =
         mutableListOf(ComposableCoroutineCreationDetector.CoroutineCreationDuringComposition)
 
+    private val coroutineBuildersStub: TestFile = kotlin(
+        """
+        package kotlinx.coroutines
+
+        object CoroutineScope
+
+        fun CoroutineScope.async(
+            block: suspend CoroutineScope.() -> Unit
+        ) {}
+
+        fun CoroutineScope.launch(
+            block: suspend CoroutineScope.() -> Unit
+        ) {}
+    """
+    )
+
     @Test
     fun errors() {
         lint().files(
@@ -94,7 +111,7 @@
                 }
             """
             ),
-            composableStub,
+            kotlin(Stubs.Composable),
             coroutineBuildersStub
         )
             .run()
@@ -215,7 +232,7 @@
                 }
             """
             ),
-            composableStub,
+            kotlin(Stubs.Composable),
             coroutineBuildersStub
         )
             .run()
@@ -319,7 +336,7 @@
                 }
             """
             ),
-            composableStub,
+            kotlin(Stubs.Composable),
             coroutineBuildersStub
         )
             .run()
diff --git a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetectorTest.kt b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetectorTest.kt
index 22f58cd..da08c10 100644
--- a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetectorTest.kt
+++ b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetectorTest.kt
@@ -18,6 +18,7 @@
 
 package androidx.compose.runtime.lint
 
+import androidx.compose.lint.Stubs
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
@@ -54,7 +55,7 @@
                 }
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expect(
@@ -90,7 +91,7 @@
                 }
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expect(
@@ -118,7 +119,7 @@
                 }
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expect(
@@ -159,7 +160,7 @@
                 }
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expect(
@@ -195,7 +196,7 @@
                 }
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expectClean()
@@ -218,7 +219,7 @@
                 ) {}
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expectClean()
@@ -239,7 +240,7 @@
                 fun FooScope.Button(foo: Int) {}
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expectClean()
@@ -260,7 +261,7 @@
                 fun Button(foo: @Composable (Int, Boolean) -> Unit) {}
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expectClean()
@@ -281,7 +282,7 @@
                 }
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expectClean()
diff --git a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableNamingDetectorTest.kt b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableNamingDetectorTest.kt
index 4e033e6..c612554 100644
--- a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableNamingDetectorTest.kt
+++ b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableNamingDetectorTest.kt
@@ -18,6 +18,7 @@
 
 package androidx.compose.runtime.lint
 
+import androidx.compose.lint.Stubs
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
@@ -50,7 +51,7 @@
                 fun button() {}
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expect(
@@ -84,7 +85,7 @@
                 fun Button() {}
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expectClean()
@@ -103,7 +104,7 @@
                 fun getInt(): Int { return 5 }
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expectClean()
@@ -122,7 +123,7 @@
                 fun GetInt(): Int { return 5 }
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expect(
diff --git a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/RememberDetectorTest.kt b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/RememberDetectorTest.kt
index 06f8db1..df54bf8 100644
--- a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/RememberDetectorTest.kt
+++ b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/RememberDetectorTest.kt
@@ -18,6 +18,7 @@
 
 package androidx.compose.runtime.lint
 
+import androidx.compose.lint.Stubs
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
@@ -107,8 +108,8 @@
                 }
             """
             ),
-            composableStub,
-            rememberStub
+            kotlin(Stubs.Composable),
+            kotlin(Stubs.Remember)
         )
             .run()
             .expect(
@@ -218,8 +219,8 @@
                 }
             """
             ),
-            composableStub,
-            rememberStub
+            kotlin(Stubs.Composable),
+            kotlin(Stubs.Remember)
         )
             .run()
             .expect(
@@ -329,8 +330,8 @@
                 }
             """
             ),
-            composableStub,
-            rememberStub
+            kotlin(Stubs.Composable),
+            kotlin(Stubs.Remember)
         )
             .run()
             .expectClean()
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt
index a54e440..9e9a53b 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt
@@ -187,7 +187,7 @@
 @OptIn(ComposeCompilerApi::class)
 // ComposeNode is a special case of readonly composable and handles creating its own groups, so
 // it is okay to use.
-@Suppress("NONREADONLY_CALL_IN_READONLY_COMPOSABLE")
+@Suppress("NONREADONLY_CALL_IN_READONLY_COMPOSABLE", "UnnecessaryLambdaCreation")
 @Composable inline fun <T : Any, reified E : Applier<*>> ComposeNode(
     noinline factory: () -> T,
     update: @DisallowComposableCalls Updater<T>.() -> Unit
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/VerticalScrollerInDrawerLayoutDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/VerticalScrollerInDrawerLayoutDemo.kt
index 54f7016..192a5b5 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/VerticalScrollerInDrawerLayoutDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/VerticalScrollerInDrawerLayoutDemo.kt
@@ -98,9 +98,7 @@
             }
         )
     ) {
-        Column {
-            content()
-        }
+        Column(content = content)
         Box(
             Modifier
                 .fillMaxHeight()
diff --git a/settings.gradle b/settings.gradle
index a728444..3fc6a86 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -263,7 +263,9 @@
 includeProject(":compose:integration-tests:docs-snippets", "compose/integration-tests/docs-snippets", [BuildType.COMPOSE])
 includeProject(":compose:integration-tests:macrobenchmark", "compose/integration-tests/macrobenchmark", [BuildType.COMPOSE])
 includeProject(":compose:integration-tests:macrobenchmark-target", "compose/integration-tests/macrobenchmark-target", [BuildType.COMPOSE])
-includeProject(":compose:internal-lint-checks", "compose/internal-lint-checks", [BuildType.COMPOSE])
+includeProject(":compose:lint", "compose/lint", [BuildType.COMPOSE])
+includeProject(":compose:lint:internal-lint-checks", "compose/lint/internal-lint-checks", [BuildType.COMPOSE])
+includeProject(":compose:lint:common", "compose/lint/common", [BuildType.COMPOSE])
 includeProject(":compose:material", "compose/material", [BuildType.COMPOSE])
 includeProject(":compose:material:material", "compose/material/material", [BuildType.COMPOSE])
 includeProject(":compose:material:material-benchmark", "compose/material/material/benchmark", [BuildType.COMPOSE])