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