blob: ffd14a5946db49c081696f4e3244ac1a8fb8064e [file] [log] [blame]
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("UnstableApiUsage")
package androidx.fragment.lint
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.LintFix
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
import com.android.tools.lint.detector.api.isKotlin
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiType
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.getContainingUClass
/**
* Lint check for detecting calls to [android.view.LayoutInflater.from]
* while being invoked from DialogFragment
*/
class UseGetLayoutInflater : Detector(), SourceCodeScanner {
companion object Issues {
val ISSUE = Issue.create(
id = "UseGetLayoutInflater",
briefDescription = "Use getLayoutInflater() to get the LayoutInflater instead of " +
"calling LayoutInflater.from(Context).",
explanation = """Using LayoutInflater.from(Context) can return a LayoutInflater \
that does not have the correct theme.""",
category = Category.CORRECTNESS,
priority = 9,
severity = Severity.WARNING,
implementation = Implementation(
UseGetLayoutInflater::class.java,
Scope.JAVA_FILE_SCOPE
),
androidSpecific = true
)
}
override fun getApplicableMethodNames(): List<String>? {
return listOf(UNWANTED_METHOD)
}
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
val containingClass = method.containingClass ?: return
val evaluator = context.evaluator
if (evaluator.getQualifiedName(containingClass) == UNWANTED_CLASS &&
evaluator.getParameterCount(method) == 1
) {
if (!isKotlin(context.psiFile)) {
startLintForJava(context, node)
} else {
startLintForKotlin(context, node)
}
}
}
private fun startLintForJava(context: JavaContext, node: UCallExpression) {
if (node.getContainingUClass()?.superClassType?.name != DIALOG_FRAGMENT_CLASS) {
return
}
val methodParameter = node.valueArguments[0].toString()
context.report(
issue = ISSUE,
location = context.getLocation(node),
message = "Use of LayoutInflater.from($methodParameter) detected. Consider using " +
"${correctMethod(context)} instead",
quickfixData = createFix(correctMethod(context), methodParameter)
)
}
private fun startLintForKotlin(context: JavaContext, node: UCallExpression) {
val classType = node.getContainingUClass()?.superClassType as? PsiType
if (
!(
classType?.presentableText == DIALOG_FRAGMENT_CLASS ||
classType.extends(context, (DIALOG_FRAGMENT_CLASS))
)
) {
return
}
context.report(
issue = ISSUE,
location = context.getLocation(node),
message = "Use of LayoutInflater.from(Context) detected. Consider using " +
"${correctMethod(context)} instead",
quickfixData = null
)
}
private fun correctMethod(context: JavaContext): String {
return if (isKotlin(context.psiFile)) {
"layoutInflater"
} else {
"getLayoutInflater()"
}
}
private fun createFix(correctMethod: String, parameter: String?): LintFix {
return fix()
.replace()
.text("LayoutInflater.from($parameter)")
.name("Replace with $correctMethod")
.with(correctMethod)
.autoFix()
.build()
}
}
private const val UNWANTED_CLASS = "android.view.LayoutInflater"
private const val UNWANTED_METHOD = "from"
private const val DIALOG_FRAGMENT_CLASS = "DialogFragment"