1 /* 2 * Copyright 2024 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.activity.compose.lint 18 19 import androidx.compose.lint.Name 20 import androidx.compose.lint.Package 21 import androidx.compose.lint.inheritsFrom 22 import com.android.tools.lint.client.api.UElementHandler 23 import com.android.tools.lint.detector.api.Category 24 import com.android.tools.lint.detector.api.Detector 25 import com.android.tools.lint.detector.api.Implementation 26 import com.android.tools.lint.detector.api.Issue 27 import com.android.tools.lint.detector.api.JavaContext 28 import com.android.tools.lint.detector.api.Scope 29 import com.android.tools.lint.detector.api.Severity 30 import com.android.tools.lint.detector.api.SourceCodeScanner 31 import com.intellij.psi.PsiClassOwner 32 import java.util.EnumSet 33 import org.jetbrains.uast.UBinaryExpressionWithType 34 import org.jetbrains.uast.UQualifiedReferenceExpression 35 import org.jetbrains.uast.tryResolveNamed 36 import org.jetbrains.uast.util.isTypeCast 37 38 class LocalContextCastIssueDetector : Detector(), SourceCodeScanner { 39 private val activityType = Name(Package("android.app"), "Activity") 40 private val contextType = Name(Package("android.content"), "Context") 41 getApplicableUastTypesnull42 override fun getApplicableUastTypes() = listOf(UBinaryExpressionWithType::class.java) 43 44 override fun createUastHandler(context: JavaContext) = 45 object : UElementHandler() { 46 override fun visitBinaryExpressionWithType(node: UBinaryExpressionWithType) { 47 // Cast expression 48 if (node.isTypeCast()) { 49 // RHS is Activity 50 if (node.type.inheritsFrom(activityType)) { 51 // LHS is Context 52 if (node.operand.getExpressionType()?.inheritsFrom(contextType) == true) { 53 // Check to see if LHS is a call to LocalContext.current - the receiver 54 // will be LocalContext 55 val resolvedReceiver = 56 (node.operand as? UQualifiedReferenceExpression) 57 ?.receiver 58 ?.tryResolveNamed() ?: return 59 if ( 60 resolvedReceiver.name == "LocalContext" && 61 (resolvedReceiver.containingFile as? PsiClassOwner) 62 ?.packageName == "androidx.compose.ui.platform" 63 ) { 64 context.report( 65 ContextCastToActivity, 66 node, 67 context.getNameLocation(node), 68 DESCRIPTION 69 ) 70 } 71 } 72 } 73 } 74 } 75 } 76 77 companion object { 78 private const val DESCRIPTION = 79 "LocalContext should not be cast to Activity, use LocalActivity instead" 80 val ContextCastToActivity = 81 Issue.create( 82 "ContextCastToActivity", 83 DESCRIPTION, 84 "Casting Context to Activity is an error as Contexts are not always Activities. Use LocalActivity instead", 85 Category.CORRECTNESS, 86 3, 87 Severity.ERROR, 88 Implementation( 89 LocalContextCastIssueDetector::class.java, 90 EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES) 91 ) 92 ) 93 } 94 } 95