1 /* 2 * Copyright 2021 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 @file:Suppress("UnstableApiUsage") 18 19 package androidx.compose.ui.lint 20 21 import androidx.compose.lint.Names 22 import androidx.compose.lint.isComposable 23 import androidx.compose.lint.isInPackageName 24 import com.android.tools.lint.detector.api.Category 25 import com.android.tools.lint.detector.api.Detector 26 import com.android.tools.lint.detector.api.Implementation 27 import com.android.tools.lint.detector.api.Issue 28 import com.android.tools.lint.detector.api.JavaContext 29 import com.android.tools.lint.detector.api.Scope 30 import com.android.tools.lint.detector.api.Severity 31 import com.android.tools.lint.detector.api.SourceCodeScanner 32 import com.intellij.psi.PsiMethod 33 import java.util.EnumSet 34 import org.jetbrains.uast.UCallExpression 35 import org.jetbrains.uast.USimpleNameReferenceExpression 36 import org.jetbrains.uast.getParameterForArgument 37 import org.jetbrains.uast.tryResolve 38 import org.jetbrains.uast.visitor.AbstractUastVisitor 39 40 /** 41 * [Detector] that checks calls to Modifier.composed to make sure they actually reference a 42 * Composable function inside - otherwise there is no reason to use Modifier.composed, and since the 43 * resulting Modifier is not skippable, it will cause worse performance. 44 */ 45 class ComposedModifierDetector : Detector(), SourceCodeScanner { getApplicableMethodNamesnull46 override fun getApplicableMethodNames(): List<String> = listOf(Names.Ui.Composed.shortName) 47 48 override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { 49 if (!method.isInPackageName(Names.Ui.PackageName)) return 50 51 val factoryLambda = 52 node.valueArguments.find { node.getParameterForArgument(it)?.name == "factory" } 53 ?: return 54 55 var hasComposableCall = false 56 factoryLambda.accept( 57 object : AbstractUastVisitor() { 58 /** Visit function calls to see if the functions are composable */ 59 override fun visitCallExpression(node: UCallExpression): Boolean = 60 (node.tryResolve() as? PsiMethod).hasComposableCall() 61 62 /** 63 * Visit any simple name reference expressions and see if they resolve to a 64 * composable function - for example if referencing a property with a composable 65 * getter, such as CompositionLocal.current. 66 */ 67 override fun visitSimpleNameReferenceExpression( 68 node: USimpleNameReferenceExpression 69 ): Boolean = (node.tryResolve() as? PsiMethod).hasComposableCall() 70 71 private fun PsiMethod?.hasComposableCall(): Boolean { 72 if (this?.isComposable == true) { 73 hasComposableCall = true 74 } 75 return hasComposableCall 76 } 77 } 78 ) 79 80 if (!hasComposableCall) { 81 context.report( 82 UnnecessaryComposedModifier, 83 node, 84 context.getNameLocation(node), 85 "Unnecessary use of Modifier.composed" 86 ) 87 } 88 } 89 90 companion object { 91 val UnnecessaryComposedModifier = 92 Issue.create( 93 "UnnecessaryComposedModifier", 94 "Modifier.composed should only be used for modifiers that invoke @Composable functions", 95 "`Modifier.composed` allows invoking @Composable functions when creating a `Modifier`" + 96 " instance - for example, using `remember` to have instance-specific state, " + 97 "allowing the same `Modifier` object to be safely used in multiple places. Using " + 98 "`Modifier.composed` without calling any @Composable functions inside is " + 99 "unnecessary, and since the Modifier is no longer skippable, this can cause a lot" + 100 " of extra work inside the composed body, leading to worse performance.", 101 Category.CORRECTNESS, 102 3, 103 Severity.WARNING, 104 Implementation( 105 ComposedModifierDetector::class.java, 106 EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES) 107 ) 108 ) 109 } 110 } 111