1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.systemui.lint
18 
19 import com.android.tools.lint.client.api.UElementHandler
20 import com.android.tools.lint.detector.api.Category
21 import com.android.tools.lint.detector.api.Detector
22 import com.android.tools.lint.detector.api.Implementation
23 import com.android.tools.lint.detector.api.Issue
24 import com.android.tools.lint.detector.api.JavaContext
25 import com.android.tools.lint.detector.api.Scope
26 import com.android.tools.lint.detector.api.Severity
27 import com.android.tools.lint.detector.api.SourceCodeScanner
28 import org.jetbrains.uast.UElement
29 import org.jetbrains.uast.UFile
30 import org.jetbrains.uast.UImportStatement
31 
32 class CollectAsStateDetector : Detector(), SourceCodeScanner {
33 
34     override fun getApplicableUastTypes(): List<Class<out UElement>> {
35         return listOf(UFile::class.java)
36     }
37 
38     override fun createUastHandler(context: JavaContext): UElementHandler {
39         return object : UElementHandler() {
40             override fun visitFile(node: UFile) {
41                 node.imports.forEach { importStatement ->
42                     visitImportStatement(context, importStatement)
43                 }
44             }
45         }
46     }
47 
48     private fun visitImportStatement(
49         context: JavaContext,
50         importStatement: UImportStatement,
51     ) {
52         val importText = importStatement.importReference?.asSourceString() ?: return
53         if (ILLEGAL_IMPORT == importText) {
54             context.report(
55                 issue = ISSUE,
56                 scope = importStatement,
57                 location = context.getLocation(importStatement),
58                 message = "collectAsState considered harmful",
59             )
60         }
61     }
62 
63     companion object {
64         @JvmField
65         val ISSUE =
66             Issue.create(
67                 id = "OverlyEagerCollectAsState",
68                 briefDescription = "collectAsState considered harmful",
69                 explanation =
70                     """
71                 go/sysui-compose#collect-as-state
72 
73                 Don't use collectAsState as it will set up a coroutine that keeps collecting from a
74                 flow until its coroutine scope becomes inactive. This prevents the work from being
75                 properly paused while the surrounding lifecycle becomes paused or stopped and is
76                 therefore considered harmful.
77 
78                 Instead, use Flow.collectAsStateWithLifecycle(initial: T) or
79                 StateFlow.collectAsStateWithLifecycle(). These APIs correctly pause the collection
80                 coroutine while the lifecycle drops below the specified minActiveState (which
81                 defaults to STARTED meaning that it will pause when the Compose-hosting window
82                 becomes invisible).
83             """
84                         .trimIndent(),
85                 category = Category.PERFORMANCE,
86                 priority = 8,
87                 severity = Severity.ERROR,
88                 implementation =
89                     Implementation(
90                         CollectAsStateDetector::class.java,
91                         Scope.JAVA_FILE_SCOPE,
92                     ),
93             )
94 
95         private val ILLEGAL_IMPORT = "androidx.compose.runtime.collectAsState"
96     }
97 }
98