Created
February 5, 2018 11:21
-
-
Save almozavr/e45001cb7f1003cc6750b92728969fd1 to your computer and use it in GitHub Desktop.
Custom UnsafeCast rule for detekt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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