Fixed a NPE in AssumeThatInsteadOfReturn inspection quickfix for empty else branches.

Fixed missing description for AssumeThatInsteadOfReturn inspection.
Added new AssertThatCollectionOrMapExpression inspection that tries to pull out methods such as isEmpty() or contains() out of an actual assertThat() expression. Added more method calls that are considered extension points.
This commit is contained in:
2019-05-06 20:17:11 +02:00
parent 0e2007641c
commit ecb5029154
19 changed files with 296 additions and 30 deletions
@@ -14,6 +14,8 @@ class AssertJClassNames {
const val DESCRIPTABLE_INTERFACE = "org.assertj.core.api.Descriptable"
@NonNls
const val EXTENSION_POINTS_INTERFACE = "org.assertj.core.api.ExtensionPoints"
@NonNls
const val OBJECT_ENUMERABLE_ASSERT_INTERFACE = "org.assertj.core.api.ObjectEnumerableAssert"
@NonNls
const val ASSERT_INTERFACE = "org.assertj.core.api.Assert"
@@ -23,7 +23,18 @@ val DESCRIBED_AS = CallMatcher.instanceCall(AssertJClassNames.DESCRIPTABLE_INTER
val WITH_REPRESENTATION_AND_SUCH = CallMatcher.instanceCall(AssertJClassNames.ASSERT_INTERFACE, "withRepresentation", "withThreadDumpOnError")!!
val USING_COMPARATOR = CallMatcher.instanceCall(AssertJClassNames.ASSERT_INTERFACE, "usingComparator", "usingDefaultComparator")!!
val IN_HEXADECIMAL_OR_BINARY = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_ASSERT_CLASSNAME, MethodNames.IN_HEXADECIMAL, MethodNames.IN_BINARY)!!
val EXTENSION_POINTS = CallMatcher.instanceCall(AssertJClassNames.EXTENSION_POINTS_INTERFACE, "is", "isNot", "has", "doesNotHave", "satisfies")!!
val EXTENSION_POINTS = CallMatcher.instanceCall(
AssertJClassNames.EXTENSION_POINTS_INTERFACE,
"is", "isNot", "has", "doesNotHave",
"satisfies"
)!!
val MORE_EXTENSION_POINTS = CallMatcher.instanceCall(
AssertJClassNames.OBJECT_ENUMERABLE_ASSERT_INTERFACE,
"are", "areNot", "have", "doNotHave",
"areAtLeast", "areAtLeastOne", "areAtMost", "areExactly",
"haveAtLeastOne", "haveAtLeast", "haveAtMost", "haveExactly",
"hasOnlyOneElementSatisfying", "anyMatch", "noneMatch", "anySatisfy", "noneSatisfy"
)!!
val NOT_ACTUAL_ASSERTIONS = CallMatcher.anyOf(
ALL_ASSERT_THAT_MATCHERS,
@@ -133,6 +133,7 @@ fun PsiExpression.getAllTheSameExpectedBooleanConstants(): Boolean? {
} else {
val isNotConstant = CallMatcher.anyOf(
EXTENSION_POINTS,
MORE_EXTENSION_POINTS,
AbstractAssertJInspection.IS_EQUAL_TO_BOOLEAN,
AbstractAssertJInspection.IS_EQUAL_TO_OBJECT,
AbstractAssertJInspection.IS_NOT_EQUAL_TO_BOOLEAN,
@@ -84,6 +84,16 @@ class MethodNames {
@NonNls
const val CONTAINS_EXACTLY = "containsExactly"
@NonNls
const val CONTAINS_ALL = "containsAll"
@NonNls
const val CONTAINS_KEY = "containsKey"
@NonNls
const val DOES_NOT_CONTAIN_KEY = "doesNotContainKey"
@NonNls
const val CONTAINS_VALUE = "containsValue"
@NonNls
const val DOES_NOT_CONTAIN_VALUE = "doesNotContainValue"
@NonNls
const val IS_EQUAL_TO_IC = "isEqualToIgnoringCase"
@NonNls
const val IS_NOT_EQUAL_TO_IC = "isNotEqualToIgnoringCase"
@@ -0,0 +1,71 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.*
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.*
import de.platon42.intellij.plugins.cajon.quickfixes.MoveOutMethodCallExpressionQuickFix
class AssertThatCollectionOrMapExpressionInspection : AbstractAssertJInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting a collection or map specific expression"
private val MAPPINGS = listOf(
Mapping(
CallMatcher.anyOf(
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_COLLECTION, "isEmpty").parameterCount(0),
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, "isEmpty").parameterCount(0)
),
MethodNames.IS_EMPTY, MethodNames.IS_NOT_EMPTY
),
Mapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_COLLECTION, "contains").parameterCount(1),
MethodNames.CONTAINS, MethodNames.DOES_NOT_CONTAIN
),
Mapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_COLLECTION, "containsAll").parameterCount(1),
MethodNames.CONTAINS_ALL, null
),
Mapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, "containsKey").parameterCount(1),
MethodNames.CONTAINS_KEY, MethodNames.DOES_NOT_CONTAIN_KEY
),
Mapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, "containsValue").parameterCount(1),
MethodNames.CONTAINS_VALUE, MethodNames.DOES_NOT_CONTAIN_VALUE
)
)
}
override fun getDisplayName() = DISPLAY_NAME
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : JavaElementVisitor() {
override fun visitExpressionStatement(statement: PsiExpressionStatement) {
super.visitExpressionStatement(statement)
if (!statement.hasAssertThat()) {
return
}
val staticMethodCall = statement.findStaticMethodCall() ?: return
if (!ASSERT_THAT_BOOLEAN.test(staticMethodCall)) {
return
}
val assertThatArgument = staticMethodCall.firstArg as? PsiMethodCallExpression ?: return
val mapping = MAPPINGS.firstOrNull { it.callMatcher.test(assertThatArgument) } ?: return
val expectedCallExpression = statement.findOutmostMethodCall() ?: return
val expectedResult = expectedCallExpression.getAllTheSameExpectedBooleanConstants() ?: return
val replacementMethod = if (expectedResult) mapping.replacementForTrue else mapping.replacementForFalse ?: return
registerMoveOutMethod(holder, expectedCallExpression, assertThatArgument, replacementMethod, ::MoveOutMethodCallExpressionQuickFix)
}
}
}
private class Mapping(
val callMatcher: CallMatcher,
val replacementForTrue: String,
val replacementForFalse: String?
)
}
@@ -13,30 +13,30 @@ class AssertThatStringExpressionInspection : AbstractAssertJInspection() {
private val MAPPINGS = listOf(
Mapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "isEmpty").parameterCount(0)!!,
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "isEmpty").parameterCount(0),
MethodNames.IS_EMPTY, MethodNames.IS_NOT_EMPTY
),
Mapping(
CallMatcher.anyOf(
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "equals").parameterCount(1)!!,
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "contentEquals").parameterCount(1)!!
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "equals").parameterCount(1),
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "contentEquals").parameterCount(1)
),
MethodNames.IS_EQUAL_TO, MethodNames.IS_NOT_EQUAL_TO
),
Mapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "equalsIgnoreCase").parameterTypes(CommonClassNames.JAVA_LANG_STRING)!!,
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "equalsIgnoreCase").parameterTypes(CommonClassNames.JAVA_LANG_STRING),
MethodNames.IS_EQUAL_TO_IC, MethodNames.IS_NOT_EQUAL_TO_IC
),
Mapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "contains").parameterCount(1)!!,
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "contains").parameterCount(1),
MethodNames.CONTAINS, MethodNames.DOES_NOT_CONTAIN
),
Mapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "startsWith").parameterTypes(CommonClassNames.JAVA_LANG_STRING)!!,
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "startsWith").parameterTypes(CommonClassNames.JAVA_LANG_STRING),
MethodNames.STARTS_WITH, MethodNames.DOES_NOT_START_WITH
),
Mapping(
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "endsWith").parameterTypes(CommonClassNames.JAVA_LANG_STRING)!!,
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "endsWith").parameterTypes(CommonClassNames.JAVA_LANG_STRING),
MethodNames.ENDS_WITH, MethodNames.DOES_NOT_END_WITH
)
)
@@ -19,20 +19,20 @@ class JoinStatementsQuickFix : AbstractCommonQuickFix(JOIN_STATEMENTS_MESSAGE) {
do {
val commentsToKeep = ArrayList<PsiComment>()
val stuffToDelete = ArrayList<PsiElement>()
var previousStatement = lastStatement.prevSibling ?: throw IllegalStateException("Internal error")
var previousStatement = lastStatement.prevSibling!!
while (previousStatement !is PsiExpressionStatement) {
if (previousStatement is PsiComment) {
commentsToKeep.add(previousStatement.copy() as PsiComment)
}
stuffToDelete.add(previousStatement)
previousStatement = previousStatement.prevSibling ?: throw IllegalStateException("Internal error")
previousStatement = previousStatement.prevSibling!!
}
stuffToDelete.forEach { if (it.isValid) it.delete() }
val statementComments = PsiTreeUtil.getChildrenOfAnyType(previousStatement, PsiComment::class.java)
commentsToKeep.addAll(statementComments)
val assertThatCallOfCursorStatement = lastStatement.findStaticMethodCall() ?: throw IllegalStateException("Internal error")
val assertThatCallOfCursorStatement = lastStatement.findStaticMethodCall()!!
val lastElementBeforeConcat = assertThatCallOfCursorStatement.parent
commentsToKeep.forEach {
@@ -34,11 +34,13 @@ class ReplaceIfByAssumeThatQuickFix(private val removeElse: Boolean) : AbstractC
val anchorElement = ifStatement.nextSibling
if (branchToKeep is PsiBlockStatement) {
val codeBlock = branchToKeep.codeBlock
val hasDeclarations = codeBlock.statements.any { it is PsiDeclarationStatement }
if (hasDeclarations) {
parentBlock.addAfter(branchToKeep, anchorElement)
} else {
parentBlock.addRangeAfter(codeBlock.firstBodyElement, codeBlock.lastBodyElement, anchorElement)
if (codeBlock.statements.isNotEmpty()) {
val hasDeclarations = codeBlock.statements.any { it is PsiDeclarationStatement }
if (hasDeclarations) {
parentBlock.addAfter(branchToKeep, anchorElement)
} else {
parentBlock.addRangeAfter(codeBlock.firstBodyElement, codeBlock.lastBodyElement, anchorElement)
}
}
} else {
parentBlock.addAfter(branchToKeep, anchorElement)