slack.lint.NonKotlinPairDetector.kt Maven / Gradle / Ivy
The newest version!
// Copyright (C) 2021 Slack Technologies, LLC
// SPDX-License-Identifier: Apache-2.0
package slack.lint
import com.android.tools.lint.client.api.UElementHandler
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.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
import com.android.tools.lint.detector.api.TextFormat
import java.util.EnumSet
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.UClass
import org.jetbrains.uast.UElement
import org.jetbrains.uast.UMethod
import org.jetbrains.uast.getContainingUClass
import org.jetbrains.uast.resolveToUElement
import org.jetbrains.uast.util.isConstructorCall
/**
* Checks to make sure that the code base will use the [kotlin.Pair] instead of any other
* alternative Pairs. Pairs might come from other APIs, so this Detector is only concerned about
* creating new Pairs.
*
* Cases that this detector should be warning about from the Java (and Kotlin) perspective.
* - new androidx.core.util.Pair()
* - androidx.core.util.Pair.create()
* - new slack.commons.Pair()
* - new android.util.Pair()
*/
class NonKotlinPairDetector : Detector(), SourceCodeScanner {
override fun getApplicableUastTypes(): List> {
return listOf(UCallExpression::class.java)
}
override fun createUastHandler(context: JavaContext): UElementHandler {
return object : UElementHandler() {
override fun visitCallExpression(node: UCallExpression) {
when {
node.isConstructorCall() -> {
checkForBannedPairType(context, node, node.resolveToUElement()?.getContainingUClass())
}
node.methodName == METHOD_NAME_PAIR_CREATE -> {
val resolved = node.resolveToUElement() ?: return
if (resolved is UMethod && resolved.isStatic) {
checkForBannedPairType(context, node, resolved.getContainingUClass())
}
}
}
}
}
}
private fun checkForBannedPairType(context: JavaContext, node: UCallExpression, uClass: UClass?) {
if (uClass?.qualifiedName in BANNED_PAIR_TYPES) {
val location = context.getLocation(node)
val issueToReport = ISSUE_KOTLIN_PAIR_NOT_CREATED
context.report(
issueToReport,
location,
issueToReport.getBriefDescription(TextFormat.TEXT),
null,
)
}
}
companion object {
private const val FQN_KOTLIN_PAIR = "kotlin.Pair"
private const val FQN_SLACK_COMMONS_PAIR = "slack.commons.Pair"
private const val FQN_ANDROIDX_PAIR = "androidx.core.util.Pair"
private const val FQN_ANDROID_DEPRECATED_PAIR = "android.util.Pair"
private const val FQN_ANDROID_PAIR = "com.android.utils"
private const val METHOD_NAME_PAIR_CREATE = "create"
private val BANNED_PAIR_TYPES =
listOf(
FQN_SLACK_COMMONS_PAIR,
FQN_ANDROIDX_PAIR,
FQN_ANDROID_DEPRECATED_PAIR,
FQN_ANDROID_PAIR,
)
/** Scope-set used for detectors which are affected by a single Java source file */
private val JAVA_FILE_AND_TEST_SOURCES_SCOPE: EnumSet =
EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
private val ISSUE_KOTLIN_PAIR_NOT_CREATED: Issue =
Issue.create(
"KotlinPairNotCreated",
"Use Kotlin's $FQN_KOTLIN_PAIR instead of other Pair types from other libraries like AndroidX and Slack commons",
"""
We should consolidate to create and use a single type of Pair in the code base. Kotlin's Pair is preferred as it \
works well with Java and Kotlin. It comes with extension functions that Slack's Pair doesn't offer.
""",
Category.CORRECTNESS,
6,
Severity.WARNING,
Implementation(NonKotlinPairDetector::class.java, JAVA_FILE_AND_TEST_SOURCES_SCOPE),
)
val issues: List = listOf(ISSUE_KOTLIN_PAIR_NOT_CREATED)
}
}