Skip to content

Instantly share code, notes, and snippets.

@NikolaDespotoski
Last active November 2, 2020 15:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NikolaDespotoski/f25a65aaa782baef99d8fca03e835b8d to your computer and use it in GitHub Desktop.
Save NikolaDespotoski/f25a65aaa782baef99d8fca03e835b8d to your computer and use it in GitHub Desktop.
RegisterForActivityResultDetector LINT detector that warns on incorrect usage of registerForActivityResult
/*
* Copyright (C) 2020 Nikola Despotoski
*
* 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.
*/
package com.example.lint.checks
import com.android.tools.lint.client.api.UElementHandler
import com.android.tools.lint.detector.api.*
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.uast.UElement
import org.jetbrains.uast.UExpression
import org.jetbrains.uast.UMethod
import org.jetbrains.uast.kotlin.KotlinUBinaryExpression
import org.jetbrains.uast.kotlin.KotlinUBlockExpression
import org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression
import org.jetbrains.uast.kotlin.declarations.KotlinUMethod
import org.jetbrains.uast.visitor.AbstractUastVisitor
/**
* Scans for incorrect usage of [Fragment.registerForActivityResult]
*/
@Suppress("UnstableApiUsage")
class RegisterForActivityResultDetector : Detector(), SourceCodeScanner {
private val visitor = RegisterForActivityResultVisitor(getApplicableMethodNames())
override fun createUastHandler(context: JavaContext): UElementHandler? = object : UElementHandler() {
override fun visitMethod(node: UMethod) {
if (node.name == ON_CREATE || node.name == ON_ATTACH || isKotlinPropertySetterOrGetter(node)) {
return
}
if (visitor.visitMethod(node)) {
context.report(ISSUE, node, context.getLocation(node), "registerForActivityResult must be called in onCreate or onAttach")
}
}
}
private fun isKotlinPropertySetterOrGetter(node: UMethod): Boolean {
return node is KotlinUMethod && node.sourcePsi is KtProperty
}
override fun getApplicableMethodNames(): List<String> = listOf(REGISTER_FOR_ACTIVITY_RESULT_FUNCTION_NAME)
override fun getApplicableUastTypes(): List<Class<out UElement>> = listOf(UMethod::class.java)
class RegisterForActivityResultVisitor(private val targetFunctionCalls: List<String>) : AbstractUastVisitor() {
override fun visitMethod(node: UMethod): Boolean {
val body = node.uastBody
if (body !is KotlinUBlockExpression) {
return false
}
val functionCalls = body.expressions.filter { it is KotlinUFunctionCallExpression || it is KotlinUBinaryExpression }
return functionCalls.any { checkBodyContainsTargetFunctionCall(targetFunctionCalls, it) }
}
private fun checkBodyContainsTargetFunctionCall(targetFunctionCalls: List<String>, functionCall: UExpression): Boolean {
val functionCallName = when (functionCall) {
is KotlinUBinaryExpression -> {
functionCall.rightOperand.sourcePsi?.run { node.firstChildNode.text }
}
is KotlinUFunctionCallExpression -> {
functionCall.sourcePsi.calleeExpression?.firstChild?.text
}
else -> null
}
return !functionCallName.isNullOrEmpty() && targetFunctionCalls.contains(functionCallName)
}
}
companion object {
private val IMPLEMENTATION = Implementation(
RegisterForActivityResultDetector::class.java,
Scope.JAVA_FILE_SCOPE
)
const val ON_CREATE = "onCreate"
const val ON_ATTACH = "onAttach"
const val REGISTER_FOR_ACTIVITY_RESULT_FUNCTION_NAME = "registerForActivityResult"
@JvmField
val ISSUE: Issue = Issue.create(
// ID: used in @SuppressLint warnings etc
id = "RegisterForActivityIncorrectCall",
// Title -- shown in the IDE's preference dialog, as category headers in the
// Analysis results window, etc
briefDescription = "Incorrect *registerForActivity* usage.",
// Full explanation of the issue; you can use some markdown markup such as
// `monospace`, *italic*, and **bold**.
explanation = """Checks for misuse of *registerForActivityResult*""",
category = Category.USABILITY,
androidSpecific = true,
priority = 6,
severity = Severity.WARNING,
implementation = IMPLEMENTATION)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment