Skip to content

Instantly share code, notes, and snippets.

@almozavr
Created February 5, 2018 11:21
Show Gist options
  • Save almozavr/e45001cb7f1003cc6750b92728969fd1 to your computer and use it in GitHub Desktop.
Save almozavr/e45001cb7f1003cc6750b92728969fd1 to your computer and use it in GitHub Desktop.
Custom UnsafeCast rule for detekt
package io.techery.detekt.extensions.rules
import io.gitlab.arturbosch.detekt.api.*
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
/**
* Whitelist-able unsafe cast check. E.g.
*
* UnsafeCast:
* whitelist: android.content.Context#getSystemService
*/
class UnsafeCast(config: Config = Config.empty) : Rule(config) {
companion object {
val WHITELIST_RECEIVER_CONFIG_KEY = "whitelistReceiver"
val WHITELIST_TARGET_CONFIG_KEY = "whitelistTarget"
}
override val issue: Issue = Issue("UnsafeCast",
Severity.Defect,
"Cast operator throws an exception if the cast is not possible.")
override fun visitBinaryWithTypeRHSExpression(expression: KtBinaryExpressionWithTypeRHS) {
val isCast = expression.operationReference.getReferencedNameElementType() === KtTokens.AS_KEYWORD
if (!isCast || !KtPsiUtil.isUnsafeCast(expression)) return
val whitelistedAsReceiver = valueOrDefault(WHITELIST_RECEIVER_CONFIG_KEY, "")
.takeIf { it.isNotEmpty() }
?.let { parseReceiverSpec(it) }
?.let { isWhitelistedAsReceiver(expression.left, it) } ?: false
val whitelistedAsTarget = valueOrDefault(WHITELIST_TARGET_CONFIG_KEY, "")
.takeIf { it.isNotEmpty() }
?.let { parseTargetSpec(it) }
?.let { isWhitelistedAsTarget(expression.right, it) } ?: false
if (whitelistedAsReceiver || whitelistedAsTarget) return
report(CodeSmell(issue, Entity.from(expression), message = ""))
}
private fun isWhitelistedAsReceiver(expression: KtExpression, specs: Collection<WhitelistReceiverSpec>): Boolean {
var instanceClass: String? = null
var expressionMethod: String? = null
// check is callee method is a part of whitelisted expression
if (expression is KtDotQualifiedExpression) {
val element = expression.lastChild
if (element is KtCallExpression) {
expressionMethod = element.calleeExpression?.text
}
}
// TODO check is instance class is a part of whitelisted expression
//
val isWhiteListed = if (/*instanceClass == null || */expressionMethod == null) false
else specs.any { it.methodLiteral == expressionMethod }
//
return isWhiteListed
}
private fun isWhitelistedAsTarget(expression: KtTypeReference?, specs: Collection<WhitelistTargetSpec>): Boolean {
var expressionClass: String? = null
expression?.let {
val element = it.lastChild
if (element is KtUserType) {
expressionClass = element.referencedName
}
}
val isWhiteListed = specs.any { it.classLiteral == expressionClass }
return isWhiteListed
}
private fun parseReceiverSpec(filter: String): Collection<WhitelistReceiverSpec> {
return filter.split(Regex("""[,;]?\s+"""))
.takeIf { it.isNotEmpty() && it[0].isNotEmpty() }
?.map {
val split = it.split("#")
if (split.size < 2) throw IllegalArgumentException("Bad format of 'ignore' config: $split")
WhitelistReceiverSpec(split[0], split[1])
} ?: emptyList()
}
private fun parseTargetSpec(filter: String): Collection<WhitelistTargetSpec> {
return filter.split(Regex("""[,;]?\s+"""))
.takeIf { it.isNotEmpty() && it[0].isNotEmpty() }
?.map {
WhitelistTargetSpec(it)
} ?: emptyList()
}
private data class WhitelistReceiverSpec(val classLiteral: String, val methodLiteral: String)
private data class WhitelistTargetSpec(val classLiteral: String)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment