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.navigation.compose.lint 20 21 import androidx.compose.lint.Name 22 import androidx.compose.lint.Package 23 import androidx.compose.lint.isInPackageName 24 import androidx.compose.lint.isNotRememberedWithKeys 25 import com.android.tools.lint.detector.api.Category 26 import com.android.tools.lint.detector.api.Detector 27 import com.android.tools.lint.detector.api.Implementation 28 import com.android.tools.lint.detector.api.Issue 29 import com.android.tools.lint.detector.api.JavaContext 30 import com.android.tools.lint.detector.api.Scope 31 import com.android.tools.lint.detector.api.Severity 32 import com.android.tools.lint.detector.api.SourceCodeScanner 33 import com.intellij.psi.PsiMethod 34 import java.util.EnumSet 35 import org.jetbrains.uast.UCallExpression 36 37 /** 38 * [Detector] that checks `getBackStackEntry` calls to make sure that if they are called inside a 39 * Composable body, they are `remember`ed with a `NavBackStackEntry` as a key. 40 */ 41 class UnrememberedGetBackStackEntryDetector : Detector(), SourceCodeScanner { getApplicableMethodNamesnull42 override fun getApplicableMethodNames(): List<String> = listOf(GetBackStackEntry.shortName) 43 44 override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { 45 if (!method.isInPackageName(PackageName)) return 46 47 if (node.isNotRememberedWithKeys(NavBackStackEntry)) { 48 context.report( 49 UnrememberedGetBackStackEntry, 50 node, 51 context.getNameLocation(node), 52 "Calling getBackStackEntry during composition without using `remember` " + 53 "with a NavBackStackEntry key" 54 ) 55 } 56 } 57 58 companion object { 59 val UnrememberedGetBackStackEntry = 60 Issue.create( 61 "UnrememberedGetBackStackEntry", 62 "Calling getBackStackEntry during composition without using `remember`" + 63 "with a NavBackStackEntry key", 64 "Backstack entries retrieved during composition need to be `remember`ed, otherwise " + 65 "they will be retrieved from the navController again, and be changed. You also " + 66 "need to pass in a key of a NavBackStackEntry to the remember call or they will " + 67 "not be updated properly. If this is in a `NavGraphBuilder.composable` scope, " + 68 "you should pass in the lambda's given entry as the key. Either hoist the state " + 69 "to an object that is not created during composition, or wrap the state in a " + 70 "call to `remember` with a `NavBackStackEntry` as a key.", 71 Category.CORRECTNESS, 72 3, 73 Severity.ERROR, 74 Implementation( 75 UnrememberedGetBackStackEntryDetector::class.java, 76 EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES) 77 ) 78 ) 79 } 80 } 81 82 private val PackageName = Package("androidx.navigation") 83 private val GetBackStackEntry = Name(PackageName, "getBackStackEntry") 84 private val NavBackStackEntry = Name(PackageName, "NavBackStackEntry") 85