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