1 /* <lambda>null2 * 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.runtime.saveable.lint 20 21 import androidx.compose.lint.Name 22 import androidx.compose.lint.Names 23 import androidx.compose.lint.Package 24 import androidx.compose.lint.inheritsFrom 25 import androidx.compose.lint.isInPackageName 26 import com.android.tools.lint.detector.api.Category 27 import com.android.tools.lint.detector.api.Detector 28 import com.android.tools.lint.detector.api.Implementation 29 import com.android.tools.lint.detector.api.Issue 30 import com.android.tools.lint.detector.api.JavaContext 31 import com.android.tools.lint.detector.api.LintFix 32 import com.android.tools.lint.detector.api.Scope 33 import com.android.tools.lint.detector.api.Severity 34 import com.android.tools.lint.detector.api.SourceCodeScanner 35 import com.intellij.psi.PsiArrayType 36 import com.intellij.psi.PsiMethod 37 import java.util.EnumSet 38 import org.jetbrains.uast.UCallExpression 39 40 /** 41 * [Detector] that checks `rememberSaveable` calls to make sure that a `Saver` is not passed to the 42 * vararg argument. 43 */ 44 class RememberSaveableDetector : Detector(), SourceCodeScanner { 45 override fun getApplicableMethodNames(): List<String> = listOf(RememberSaveable.shortName) 46 47 override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { 48 if (!method.isInPackageName(RuntimeSaveablePackageName)) return 49 50 val argumentMapping = context.evaluator.computeArgumentMapping(node, method) 51 // Filter the arguments provided to those that correspond to the varargs parameter. 52 val varargArguments = 53 argumentMapping 54 .toList() 55 .filter { (_, parameter) -> 56 // TODO: https://youtrack.jetbrains.com/issue/KT-45700 parameter.isVarArgs 57 // returns false because only varargs parameters at the end of a function are 58 // considered varargs in PSI, since Java callers can not call them as a vararg 59 // parameter otherwise. This is true for both KtLightParameterImpl, and 60 // ClsParameterImpl, so if we wanted to actually find if the parameter was 61 // vararg for Kotlin callers we would instead need to look through the metadata 62 // on 63 // the class file, or the source KtParameter. 64 // Instead since they are just treated as an array type in PSI, just find the 65 // corresponding array parameter. 66 parameter.type is PsiArrayType 67 } 68 .map { 69 // We don't need the parameter anymore, so just return the argument 70 it.first 71 } 72 73 // Ignore if there are no vararg arguments provided, and ignore if there are multiple (we 74 // assume if that multiple are provided then the developer knows what they are doing) 75 if (varargArguments.size != 1) return 76 77 val argument = varargArguments.first() 78 val argumentType = argument.getExpressionType() 79 80 // Ignore if the expression isn't a `Saver` 81 if (argumentType?.inheritsFrom(Saver) != true) return 82 83 // If the type is a MutableState, there is a second overload with a differently 84 // named parameter we should use instead 85 val isMutableStateSaver = 86 node.getExpressionType()?.inheritsFrom(Names.Runtime.MutableState) == true 87 88 // TODO: might be safer to try and find the other overload through PSI, and get 89 // the parameter name directly. 90 val parameterName = 91 if (isMutableStateSaver) { 92 "stateSaver" 93 } else { 94 "saver" 95 } 96 97 val argumentText = argument.sourcePsi?.text 98 99 context.report( 100 RememberSaveableSaverParameter, 101 node, 102 context.getLocation(argument), 103 "Passing `Saver` instance to vararg `inputs`", 104 argumentText?.let { 105 val replacement = "$parameterName = $argumentText" 106 LintFix.create() 107 .replace() 108 .name("Change to `$replacement`") 109 .text(argumentText) 110 .with(replacement) 111 .autoFix() 112 .build() 113 } 114 ) 115 } 116 117 companion object { 118 val RememberSaveableSaverParameter = 119 Issue.create( 120 "RememberSaveableSaverParameter", 121 "`Saver` objects should be passed to the saver parameter, not the vararg " + 122 "`inputs` parameter", 123 "The first parameter to `rememberSaveable` is a vararg parameter for inputs that when" + 124 " changed will cause the state to reset. Passing a `Saver` object to this " + 125 "parameter is an error, as the intention is to pass the `Saver` object to the " + 126 "saver parameter. Since the saver parameter is not the first parameter, it must " + 127 "be explicitly named.", 128 Category.CORRECTNESS, 129 3, 130 Severity.ERROR, 131 Implementation( 132 RememberSaveableDetector::class.java, 133 EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES) 134 ) 135 ) 136 } 137 } 138 139 private val RuntimeSaveablePackageName = Package("androidx.compose.runtime.saveable") 140 private val RememberSaveable = Name(RuntimeSaveablePackageName, "rememberSaveable") 141 private val Saver = Name(RuntimeSaveablePackageName, "Saver") 142