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