Improve Collection/Array/String detection logic in intentions

Use static methods from KotlinBuiltIns and check all supertypes, support cases
of Collection, Map and CharSequence
This commit is contained in:
Alexander Udalov
2016-10-21 16:04:49 +03:00
parent a5a422fc4d
commit 2076a31094
11 changed files with 109 additions and 45 deletions
@@ -26,9 +26,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.js.descriptorUtils.nameIfStandardType
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.typeRefHelpers.setReceiverTypeReference
import org.jetbrains.kotlin.resolve.BindingContext
@@ -235,42 +233,31 @@ private fun KtExpression.ifBranchesOrThis(): List<KtExpression?> {
return listOf(then) + `else`?.ifBranchesOrThis().orEmpty()
}
private val arrayName = Name.identifier("Array")
private val collectionName = Name.identifier("Collection")
private val stringName = Name.identifier("String")
fun ResolvedCall<out CallableDescriptor>.resolvedToArrayType() =
resultingDescriptor.returnType?.nameIfStandardType == arrayName
fun ResolvedCall<out CallableDescriptor>.resolvedToCollectionType() =
resultingDescriptor.returnType?.constructor?.supertypes?.firstOrNull {
it.nameIfStandardType == collectionName
} != null
fun ResolvedCall<out CallableDescriptor>.resolvedToStringType() =
resultingDescriptor.returnType?.nameIfStandardType == stringName
fun ResolvedCall<out CallableDescriptor>.resolvedToArrayType(): Boolean =
resultingDescriptor.returnType.let { type ->
type != null && (KotlinBuiltIns.isArray(type) || KotlinBuiltIns.isPrimitiveArray(type))
}
fun KtElement?.isZero() = this?.text == "0"
fun KtElement?.isOne() = this?.text == "1"
private fun KtExpression.isExpressionOfTypeOrSubtype(predicate: (KotlinType) -> Boolean): Boolean {
val returnType = getResolvedCall(analyze())?.resultingDescriptor?.returnType
return returnType != null && (returnType.constructor.supertypes + returnType).any(predicate)
}
fun KtElement?.isSizeOrLength(): Boolean {
if (this !is KtDotQualifiedExpression) return false
return when (selectorExpression?.text) {
"size" -> receiverExpression.isArrayOrCollection()
"length" -> receiverExpression.isString()
"size" -> receiverExpression.isExpressionOfTypeOrSubtype { type ->
KotlinBuiltIns.isArray(type) ||
KotlinBuiltIns.isPrimitiveArray(type) ||
KotlinBuiltIns.isCollectionOrNullableCollection(type) ||
KotlinBuiltIns.isMapOrNullableMap(type)
}
"length" -> receiverExpression.isExpressionOfTypeOrSubtype(KotlinBuiltIns::isCharSequenceOrNullableCharSequence)
else -> false
}
}
private fun KtExpression.isArrayOrCollection(): Boolean {
val resolvedCall = getResolvedCall(analyze()) ?: return false
return resolvedCall.resolvedToArrayType() || resolvedCall.resolvedToCollectionType()
}
private fun KtExpression.isString(): Boolean {
val resolvedCall = getResolvedCall(analyze()) ?: return false
return resolvedCall.resolvedToStringType()
}
@@ -0,0 +1,8 @@
// WITH_RUNTIME
// INTENTION_TEXT: Replace '==' with 'Arrays.equals'
fun foo() {
val a = charArrayOf('a', 'b', 'c')
val b = charArrayOf('a', 'b', 'c')
if (a <caret>== b) {
}
}
@@ -0,0 +1,10 @@
import java.util.Arrays
// WITH_RUNTIME
// INTENTION_TEXT: Replace '==' with 'Arrays.equals'
fun foo() {
val a = charArrayOf('a', 'b', 'c')
val b = charArrayOf('a', 'b', 'c')
if (Arrays.equals(a, b)) {
}
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun foo() {
val c: Collection<String> = listOf("")
c.size<caret> > 0
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun foo() {
val c: Collection<String> = listOf("")
c.isNotEmpty()
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun foo() {
val m = mapOf("" to 1)
m.size<caret> > 0
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun foo() {
val m = mapOf("" to 1)
m.isNotEmpty()
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun foo() {
val a = intArrayOf(1, 2, 3)
a.size<caret> > 0
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun foo() {
val a = intArrayOf(1, 2, 3)
a.isNotEmpty()
}
@@ -11173,6 +11173,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceArrayEqualityOpWithArraysEquals/arrayEQEQ.kt");
doTest(fileName);
}
@TestMetadata("primitiveArrayEQEQ.kt")
public void testPrimitiveArrayEQEQ() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceArrayEqualityOpWithArraysEquals/primitiveArrayEQEQ.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/intentions/replaceExplicitFunctionLiteralParamWithIt")
@@ -11567,6 +11573,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName);
}
@TestMetadata("collection.kt")
public void testCollection() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSizeCheckWithIsNotEmpty/collection.kt");
doTest(fileName);
}
@TestMetadata("gt.kt")
public void testGt() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSizeCheckWithIsNotEmpty/gt.kt");
@@ -11603,6 +11615,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName);
}
@TestMetadata("map.kt")
public void testMap() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSizeCheckWithIsNotEmpty/map.kt");
doTest(fileName);
}
@TestMetadata("oppositeSign.kt")
public void testOppositeSign() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSizeCheckWithIsNotEmpty/oppositeSign.kt");
@@ -11615,6 +11633,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName);
}
@TestMetadata("primitiveArray.kt")
public void testPrimitiveArray() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSizeCheckWithIsNotEmpty/primitiveArray.kt");
doTest(fileName);
}
@TestMetadata("set.kt")
public void testSet() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSizeCheckWithIsNotEmpty/set.kt");