1 /*
2  * Copyright 2023 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 androidx.compose.lint
18 
19 import com.android.tools.lint.detector.api.Category
20 import com.android.tools.lint.detector.api.Detector
21 import com.android.tools.lint.detector.api.Implementation
22 import com.android.tools.lint.detector.api.Issue
23 import com.android.tools.lint.detector.api.JavaContext
24 import com.android.tools.lint.detector.api.Scope
25 import com.android.tools.lint.detector.api.Severity
26 import com.android.tools.lint.detector.api.SourceCodeScanner
27 import com.intellij.psi.PsiMethod
28 import java.util.EnumSet
29 import org.jetbrains.uast.UCallExpression
30 import org.jetbrains.uast.getParameterForArgument
31 
32 class ExceptionMessageDetector : Detector(), SourceCodeScanner {
33 
getApplicableMethodNamesnull34     override fun getApplicableMethodNames(): List<String> =
35         listOf(Check, CheckNotNull, Require, RequireNotNull).map { it.shortName }
36 
visitMethodCallnull37     override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
38 
39         // We ignore other functions with the same name.
40         if (!method.isInPackageName(KotlinPackage)) return
41 
42         val lazyMessage =
43             node.valueArguments.find { node.getParameterForArgument(it)?.name == "lazyMessage" }
44         if (lazyMessage == null) {
45             context.report(
46                 ISSUE,
47                 node,
48                 context.getNameLocation(node),
49                 "Please specify a lazyMessage param for ${node.methodName}",
50             )
51         }
52     }
53 
54     companion object {
55         val KotlinPackage = Package("kotlin")
56         val Check = Name(KotlinPackage, "check")
57         val CheckNotNull = Name(KotlinPackage, "checkNotNull")
58         val Require = Name(KotlinPackage, "require")
59         val RequireNotNull = Name(KotlinPackage, "requireNotNull")
60         val ISSUE =
61             Issue.create(
62                 id = "ExceptionMessage",
63                 briefDescription = "Please provide a string for the lazyMessage parameter",
64                 explanation =
65                     """
66                 Calls to check(), checkNotNull(), require() and requireNotNull() should
67                 include a message string that can be used to debug issues experienced
68                 by users.
69 
70                 When we read user-supplied logs, the line numbers are sometimes not
71                 sufficient to determine the cause of a bug. Inline functions can
72                 sometimes make it hard to determine which file threw an exception.
73                 Consider supplying a lazyMessage parameter to identify the check()
74                 or require() call.
75             """,
76                 category = Category.CORRECTNESS,
77                 priority = 3,
78                 severity = Severity.ERROR,
79                 implementation =
80                     Implementation(
81                         ExceptionMessageDetector::class.java,
82                         EnumSet.of(Scope.JAVA_FILE)
83                     )
84             )
85     }
86 }
87