1 /*
2  * 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.detector.api.Category
22 import com.android.tools.lint.detector.api.Detector
23 import com.android.tools.lint.detector.api.Implementation
24 import com.android.tools.lint.detector.api.Incident
25 import com.android.tools.lint.detector.api.Issue
26 import com.android.tools.lint.detector.api.JavaContext
27 import com.android.tools.lint.detector.api.Scope
28 import com.android.tools.lint.detector.api.Severity
29 import com.intellij.psi.PsiMethod
30 import org.jetbrains.uast.UCallExpression
31 
32 class ObsoleteBuildCompatUsageDetector : Detector(), Detector.UastScanner {
33     private val methodsToApiLevels =
34         mapOf(
35             "isAtLeastN" to 24,
36             "isAtLeastNMR1" to 25,
37             "isAtLeastO" to 26,
38             "isAtLeastOMR1" to 27,
39             "isAtLeastP" to 28,
40             "isAtLeastQ" to 29
41         )
42 
getApplicableMethodNamesnull43     override fun getApplicableMethodNames() = methodsToApiLevels.keys.toList()
44 
45     override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
46         if (!context.evaluator.isMemberInClass(method, "androidx.core.os.BuildCompat")) {
47             return
48         }
49 
50         // A receiver indicates the class name is part of the call (as opposed to static import).
51         val target = if (node.receiver != null) node.uastParent!! else node
52 
53         val apiLevel = methodsToApiLevels[node.methodName]
54         val lintFix =
55             fix()
56                 .name("Use SDK_INT >= $apiLevel")
57                 .replace()
58                 .text(target.asRenderString())
59                 .with("Build.VERSION.SDK_INT >= $apiLevel")
60                 .build()
61         val incident =
62             Incident(context)
63                 .fix(lintFix)
64                 .issue(ISSUE)
65                 .location(context.getLocation(node))
66                 .message("Using deprecated BuildCompat methods")
67                 .scope(node)
68         context.report(incident)
69     }
70 
71     companion object {
72         val ISSUE =
73             Issue.create(
74                 "ObsoleteBuildCompat",
75                 "Using deprecated BuildCompat methods",
76                 "BuildConfig methods should only be used prior to an API level's finalization. " +
77                     "Once an API level number is assigned, comparing directly with SDK_INT " +
78                     "is preferred as it enables other lint checks to correctly work.",
79                 Category.CORRECTNESS,
80                 5,
81                 Severity.ERROR,
82                 Implementation(ObsoleteBuildCompatUsageDetector::class.java, Scope.JAVA_FILE_SCOPE)
83             )
84     }
85 }
86