1 /*
<lambda>null2  * Copyright (C) 2018 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.build.lint
20 
21 import com.android.tools.lint.client.api.UElementHandler
22 import com.android.tools.lint.detector.api.Category
23 import com.android.tools.lint.detector.api.Detector
24 import com.android.tools.lint.detector.api.Implementation
25 import com.android.tools.lint.detector.api.Incident
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 org.jetbrains.kotlin.psi.KtAnnotationEntry
31 import org.jetbrains.uast.UAnnotation
32 import org.jetbrains.uast.UCallExpression
33 import org.jetbrains.uast.UReferenceExpression
34 import org.jetbrains.uast.util.isArrayInitializer
35 
36 class BanRestrictToTestsScope : Detector(), Detector.UastScanner {
37 
38     override fun getApplicableUastTypes() = listOf(UAnnotation::class.java)
39 
40     override fun createUastHandler(context: JavaContext): UElementHandler {
41         return AnnotationChecker(context)
42     }
43 
44     private inner class AnnotationChecker(val context: JavaContext) : UElementHandler() {
45         override fun visitAnnotation(node: UAnnotation) {
46             if (node.qualifiedName != "androidx.annotation.RestrictTo") return
47 
48             // Resolve the FQN for all arguments to value parameter.
49             val scopes =
50                 node
51                     .findAttributeValue("value")
52                     ?.let { value ->
53                         if (value.isArrayInitializer()) {
54                             (value as? UCallExpression)?.valueArguments?.mapNotNull { arg ->
55                                 arg as? UReferenceExpression
56                             } ?: emptyList()
57                         } else if (value is UReferenceExpression) {
58                             listOfNotNull(value)
59                         } else {
60                             emptyList()
61                         }
62                     }
63                     ?.mapNotNull { expr -> expr.resolve()?.getFqName() } ?: emptyList()
64 
65             if (!scopes.contains("androidx.annotation.RestrictTo.Scope.TESTS")) return
66 
67             val incident =
68                 Incident(context)
69                     .issue(ISSUE)
70                     .location(context.getNameLocation(node))
71                     .message("Replace `@RestrictTo(TESTS)` with `@VisibleForTesting`")
72                     .scope(node)
73 
74             // If there's only one scope, suggest replacement.
75             if (scopes.size == 1) {
76                 // Extract Kotlin use-site target, if available.
77                 val useSiteTarget =
78                     (node.sourcePsi as? KtAnnotationEntry)
79                         ?.useSiteTarget
80                         ?.getAnnotationUseSiteTarget()
81                         ?.renderName
82                         ?.let { "$it:" } ?: ""
83 
84                 val fix =
85                     fix()
86                         .name("Replace with `@${useSiteTarget}VisibleForTesting`")
87                         .replace()
88                         .with("@${useSiteTarget}androidx.annotation.VisibleForTesting")
89                         .shortenNames()
90                         .build()
91                 incident.fix(fix)
92             }
93 
94             context.report(incident)
95         }
96     }
97 
98     companion object {
99         val ISSUE =
100             Issue.create(
101                 "UsesRestrictToTestsScope",
102                 "Uses @RestrictTo(TESTS) restriction scope",
103                 "Use of @RestrictTo(TESTS) restriction scope is not allowed, use " +
104                     "@VisibleForTesting instead.",
105                 Category.CORRECTNESS,
106                 5,
107                 Severity.ERROR,
108                 Implementation(BanRestrictToTestsScope::class.java, Scope.JAVA_FILE_SCOPE)
109             )
110     }
111 }
112