Heavily reworked inspections for edge cases, such as multiple isEqualTo() calls inside a single statement. Corrected highlighting for most inspections. Lots of refactorings, tweakings. Added travis-ci file (untested). Added jacoco.
This commit is contained in:
+29
-62
@@ -3,6 +3,7 @@ package de.platon42.intellij.plugins.cajon.inspections
|
||||
import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool
|
||||
import com.intellij.codeInspection.LocalQuickFix
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.tree.IElementType
|
||||
@@ -14,10 +15,10 @@ import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_C
|
||||
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_ENUMERABLE_ASSERT_CLASSNAME
|
||||
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_INTEGER_ASSERT_CLASSNAME
|
||||
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ASSERTIONS_CLASSNAME
|
||||
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ASSERT_INTERFACE
|
||||
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.GUAVA_ASSERTIONS_CLASSNAME
|
||||
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.GUAVA_OPTIONAL_CLASSNAME
|
||||
import de.platon42.intellij.plugins.cajon.MethodNames
|
||||
import de.platon42.intellij.plugins.cajon.getArg
|
||||
import de.platon42.intellij.plugins.cajon.qualifierExpression
|
||||
import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceSimpleMethodCallQuickFix
|
||||
|
||||
@@ -28,8 +29,9 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
|
||||
const val MORE_CONCISE_MESSAGE_TEMPLATE = "%s() would be more concise than %s()"
|
||||
|
||||
const val REPLACE_DESCRIPTION_TEMPLATE = "Replace %s() with %s()"
|
||||
const val REMOVE_EXPECTED_OUTMOST_DESCRIPTION_TEMPLATE = "Remove unwrapping of expected expression and replace %s() with %s()"
|
||||
const val UNWRAP_ACTUAL_OUTMOST_DESCRIPTION_TEMPLATE = "Unwrap actual expression and replace %s() with %s()"
|
||||
const val REMOVE_EXPECTED_OUTMOST_DESCRIPTION_TEMPLATE = "Unwrap expected expression and replace %s() with %s()"
|
||||
const val MOVE_ACTUAL_EXPRESSION_DESCRIPTION_TEMPLATE = "Remove %s() of actual expression and use assertThat().%s() instead"
|
||||
const val MOVING_OUT_MESSAGE_TEMPLATE = "Moving %s() expression out of assertThat() would be more concise"
|
||||
|
||||
val TOKEN_TO_ASSERTJ_FOR_PRIMITIVE_MAP = mapOf<IElementType, String>(
|
||||
JavaTokenType.EQEQ to MethodNames.IS_EQUAL_TO,
|
||||
@@ -74,21 +76,21 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
|
||||
val ASSERT_THAT_JAVA8_OPTIONAL = CallMatcher.staticCall(ASSERTIONS_CLASSNAME, MethodNames.ASSERT_THAT)
|
||||
.parameterTypes(CommonClassNames.JAVA_UTIL_OPTIONAL)!!
|
||||
|
||||
val ASSERT_THAT_GUAVA_OPTIONAL = CallMatcher.staticCall(GUAVA_ASSERTIONS_CLASSNAME, MethodNames.ASSERT_THAT)
|
||||
.parameterTypes(GUAVA_OPTIONAL_CLASSNAME)!!
|
||||
val GUAVA_ASSERT_THAT_ANY = CallMatcher.staticCall(GUAVA_ASSERTIONS_CLASSNAME, MethodNames.ASSERT_THAT)
|
||||
.parameterCount(1)!!
|
||||
|
||||
val IS_EQUAL_TO_OBJECT = CallMatcher.instanceCall(ABSTRACT_ASSERT_CLASSNAME, MethodNames.IS_EQUAL_TO)
|
||||
val IS_EQUAL_TO_OBJECT = CallMatcher.instanceCall(ASSERT_INTERFACE, MethodNames.IS_EQUAL_TO)
|
||||
.parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!!
|
||||
val IS_NOT_EQUAL_TO_OBJECT = CallMatcher.instanceCall(ABSTRACT_ASSERT_CLASSNAME, MethodNames.IS_NOT_EQUAL_TO)
|
||||
val IS_NOT_EQUAL_TO_OBJECT = CallMatcher.instanceCall(ASSERT_INTERFACE, MethodNames.IS_NOT_EQUAL_TO)
|
||||
.parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!!
|
||||
val IS_EQUAL_TO_BOOLEAN = CallMatcher.instanceCall(ABSTRACT_BOOLEAN_ASSERT_CLASSNAME, MethodNames.IS_EQUAL_TO)
|
||||
.parameterTypes("boolean")!!
|
||||
val IS_NOT_EQUAL_TO_BOOLEAN =
|
||||
CallMatcher.instanceCall(ABSTRACT_BOOLEAN_ASSERT_CLASSNAME, MethodNames.IS_NOT_EQUAL_TO)
|
||||
.parameterTypes("boolean")!!
|
||||
val IS_SAME_AS_OBJECT = CallMatcher.instanceCall(ABSTRACT_ASSERT_CLASSNAME, MethodNames.IS_SAME_AS)
|
||||
val IS_SAME_AS_OBJECT = CallMatcher.instanceCall(ASSERT_INTERFACE, MethodNames.IS_SAME_AS)
|
||||
.parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!!
|
||||
val IS_NOT_SAME_AS_OBJECT = CallMatcher.instanceCall(ABSTRACT_ASSERT_CLASSNAME, MethodNames.IS_NOT_SAME_AS)
|
||||
val IS_NOT_SAME_AS_OBJECT = CallMatcher.instanceCall(ASSERT_INTERFACE, MethodNames.IS_NOT_SAME_AS)
|
||||
.parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!!
|
||||
|
||||
val HAS_SIZE = CallMatcher.instanceCall(ABSTRACT_ENUMERABLE_ASSERT_CLASSNAME, MethodNames.HAS_SIZE)
|
||||
@@ -174,7 +176,22 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
|
||||
val description = REPLACE_DESCRIPTION_TEMPLATE.format(originalMethod, replacementMethod)
|
||||
val message = SIMPLIFY_MESSAGE_TEMPLATE.format(originalMethod, replacementMethod)
|
||||
val quickFix = ReplaceSimpleMethodCallQuickFix(description, replacementMethod)
|
||||
holder.registerProblem(expression, message, quickFix)
|
||||
val textRange = TextRange(expression.qualifierExpression.textLength, expression.textLength)
|
||||
holder.registerProblem(expression, textRange, message, quickFix)
|
||||
}
|
||||
|
||||
protected fun registerMoveOutMethod(
|
||||
holder: ProblemsHolder,
|
||||
expression: PsiMethodCallExpression,
|
||||
oldActualExpression: PsiMethodCallExpression,
|
||||
replacementMethod: String,
|
||||
quickFixSupplier: (String, String) -> LocalQuickFix
|
||||
) {
|
||||
val originalMethod = getOriginalMethodName(oldActualExpression) ?: return
|
||||
val description = MOVE_ACTUAL_EXPRESSION_DESCRIPTION_TEMPLATE.format(originalMethod, replacementMethod)
|
||||
val message = MOVING_OUT_MESSAGE_TEMPLATE.format(originalMethod)
|
||||
val quickfix = quickFixSupplier(description, replacementMethod)
|
||||
holder.registerProblem(expression, message, quickfix)
|
||||
}
|
||||
|
||||
protected fun registerReplaceMethod(
|
||||
@@ -199,7 +216,8 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
|
||||
val description = descriptionTemplate.format(originalMethod, replacementMethod)
|
||||
val message = MORE_CONCISE_MESSAGE_TEMPLATE.format(replacementMethod, originalMethod)
|
||||
val quickfix = quickFixSupplier(description, replacementMethod)
|
||||
holder.registerProblem(expression, message, quickfix)
|
||||
val textRange = TextRange(expression.qualifierExpression.textLength, expression.textLength)
|
||||
holder.registerProblem(expression, textRange, message, quickfix)
|
||||
}
|
||||
|
||||
protected fun registerRemoveExpectedOutmostMethod(
|
||||
@@ -211,55 +229,4 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
|
||||
) {
|
||||
registerConciseMethod(REMOVE_EXPECTED_OUTMOST_DESCRIPTION_TEMPLATE, holder, expression, oldExpectedCallExpression, replacementMethod, quickFixSupplier)
|
||||
}
|
||||
|
||||
protected fun registerRemoveActualOutmostMethod(
|
||||
holder: ProblemsHolder,
|
||||
expression: PsiMethodCallExpression,
|
||||
oldExpectedCallExpression: PsiMethodCallExpression,
|
||||
replacementMethod: String,
|
||||
quickFixSupplier: (String, String) -> LocalQuickFix
|
||||
) {
|
||||
registerConciseMethod(UNWRAP_ACTUAL_OUTMOST_DESCRIPTION_TEMPLATE, holder, expression, oldExpectedCallExpression, replacementMethod, quickFixSupplier)
|
||||
}
|
||||
|
||||
protected fun calculateConstantParameterValue(expression: PsiMethodCallExpression, argIndex: Int): Any? {
|
||||
if (argIndex >= expression.argumentList.expressions.size) return null
|
||||
val valueExpression = expression.getArg(argIndex)
|
||||
val constantEvaluationHelper = JavaPsiFacade.getInstance(expression.project).constantEvaluationHelper
|
||||
val value = constantEvaluationHelper.computeConstantExpression(valueExpression)
|
||||
if (value == null) {
|
||||
val field = (valueExpression as? PsiReferenceExpression)?.resolve() as? PsiField
|
||||
if (field?.containingClass?.qualifiedName == CommonClassNames.JAVA_LANG_BOOLEAN) {
|
||||
return when (field.name) {
|
||||
"TRUE" -> true
|
||||
"FALSE" -> false
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
protected fun getExpectedBooleanResult(expectedCallExpression: PsiMethodCallExpression): Boolean? {
|
||||
val isTrue = IS_TRUE.test(expectedCallExpression)
|
||||
val isFalse = IS_FALSE.test(expectedCallExpression)
|
||||
if (isTrue || isFalse) {
|
||||
return isTrue
|
||||
} else {
|
||||
val isEqualTo = IS_EQUAL_TO_BOOLEAN.test(expectedCallExpression) || IS_EQUAL_TO_OBJECT.test(expectedCallExpression)
|
||||
val isNotEqualTo = IS_NOT_EQUAL_TO_BOOLEAN.test(expectedCallExpression) || IS_NOT_EQUAL_TO_OBJECT.test(expectedCallExpression)
|
||||
if (isEqualTo || isNotEqualTo) {
|
||||
val constValue = calculateConstantParameterValue(expectedCallExpression, 0) as? Boolean ?: return null
|
||||
return isNotEqualTo xor constValue
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
protected fun hasAssertJMethod(element: PsiElement, classname: String, methodname: String): Boolean {
|
||||
val findClass =
|
||||
JavaPsiFacade.getInstance(element.project).findClass(classname, GlobalSearchScope.allScope(element.project))
|
||||
?: return false
|
||||
return findClass.allMethods.any { it.name == methodname }
|
||||
}
|
||||
}
|
||||
-4
@@ -7,10 +7,6 @@ import org.jetbrains.annotations.NonNls
|
||||
open class AbstractJUnitAssertInspection : AbstractBaseJavaLocalInspectionTool() {
|
||||
|
||||
companion object {
|
||||
const val CONVERT_MESSAGE_TEMPLATE = "%s can be converted to AssertJ style"
|
||||
|
||||
const val REPLACE_DESCRIPTION_TEMPLATE = "Replace %s() with assertThat().%s()"
|
||||
|
||||
@NonNls
|
||||
const val JUNIT_ASSERT_CLASSNAME = "org.junit.Assert"
|
||||
|
||||
|
||||
+13
-15
@@ -4,11 +4,8 @@ import com.intellij.codeInspection.LocalQuickFix
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.util.TypeConversionUtil
|
||||
import de.platon42.intellij.plugins.cajon.MethodNames
|
||||
import de.platon42.intellij.plugins.cajon.findOutmostMethodCall
|
||||
import de.platon42.intellij.plugins.cajon.firstArg
|
||||
import de.platon42.intellij.plugins.cajon.map
|
||||
import de.platon42.intellij.plugins.cajon.quickfixes.MoveActualOuterExpressionMethodCallQuickFix
|
||||
import de.platon42.intellij.plugins.cajon.*
|
||||
import de.platon42.intellij.plugins.cajon.quickfixes.MoveOutMethodCallExpressionQuickFix
|
||||
import de.platon42.intellij.plugins.cajon.quickfixes.SplitBinaryExpressionMethodCallQuickFix
|
||||
|
||||
class AssertThatBinaryExpressionInspection : AbstractAssertJInspection() {
|
||||
@@ -23,19 +20,20 @@ class AssertThatBinaryExpressionInspection : AbstractAssertJInspection() {
|
||||
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
||||
return object : JavaElementVisitor() {
|
||||
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
|
||||
super.visitMethodCallExpression(expression)
|
||||
if (!ASSERT_THAT_BOOLEAN.test(expression)) {
|
||||
override fun visitExpressionStatement(statement: PsiExpressionStatement?) {
|
||||
super.visitExpressionStatement(statement)
|
||||
val staticMethodCall = statement?.findStaticMethodCall() ?: return
|
||||
if (!ASSERT_THAT_BOOLEAN.test(staticMethodCall)) {
|
||||
return
|
||||
}
|
||||
|
||||
val expectedCallExpression = expression.findOutmostMethodCall() ?: return
|
||||
val expectedResult = getExpectedBooleanResult(expectedCallExpression) ?: return
|
||||
val expectedCallExpression = statement.findOutmostMethodCall() ?: return
|
||||
val expectedResult = expectedCallExpression.getAllTheSameExpectedBooleanConstants() ?: return
|
||||
|
||||
val assertThatArgument = expression.firstArg
|
||||
val assertThatArgument = staticMethodCall.firstArg
|
||||
if (assertThatArgument is PsiMethodCallExpression && OBJECT_EQUALS.test(assertThatArgument)) {
|
||||
val replacementMethod = expectedResult.map(MethodNames.IS_EQUAL_TO, MethodNames.IS_NOT_EQUAL_TO)
|
||||
registerSplitMethod(holder, expression, "${MethodNames.EQUALS}()", replacementMethod, ::MoveActualOuterExpressionMethodCallQuickFix)
|
||||
registerSplitMethod(holder, expectedCallExpression, "${MethodNames.EQUALS}()", replacementMethod, ::MoveOutMethodCallExpressionQuickFix)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -50,7 +48,7 @@ class AssertThatBinaryExpressionInspection : AbstractAssertJInspection() {
|
||||
return
|
||||
} else if (isLeftNull || isRightNull) {
|
||||
val replacementMethod = expectedResult.map(MethodNames.IS_NULL, MethodNames.IS_NOT_NULL)
|
||||
registerSplitMethod(holder, expression, "binary", replacementMethod) { desc, method ->
|
||||
registerSplitMethod(holder, expectedCallExpression, "binary", replacementMethod) { desc, method ->
|
||||
SplitBinaryExpressionMethodCallQuickFix(desc, method, pickRightOperand = isLeftNull, noExpectedExpression = true)
|
||||
}
|
||||
return
|
||||
@@ -58,7 +56,7 @@ class AssertThatBinaryExpressionInspection : AbstractAssertJInspection() {
|
||||
|
||||
val isPrimitive = bothTypes.all(TypeConversionUtil::isPrimitiveAndNotNull)
|
||||
val isNumericType = bothTypes.all(TypeConversionUtil::isNumericType)
|
||||
val constantEvaluationHelper = JavaPsiFacade.getInstance(expression.project).constantEvaluationHelper
|
||||
val constantEvaluationHelper = JavaPsiFacade.getInstance(statement.project).constantEvaluationHelper
|
||||
val swapExpectedAndActual = constantEvaluationHelper.computeConstantExpression(binaryExpression.lOperand) != null
|
||||
|
||||
val tokenType = binaryExpression.operationTokenType
|
||||
@@ -72,7 +70,7 @@ class AssertThatBinaryExpressionInspection : AbstractAssertJInspection() {
|
||||
(isPrimitive || isNumericType).map(TOKEN_TO_ASSERTJ_FOR_PRIMITIVE_MAP, TOKEN_TO_ASSERTJ_FOR_OBJECT_MAPPINGS)
|
||||
val replacementMethod = mappingToUse[tokenType] ?: return
|
||||
|
||||
registerSplitMethod(holder, expression, "binary", replacementMethod) { desc, method ->
|
||||
registerSplitMethod(holder, expectedCallExpression, "binary", replacementMethod) { desc, method ->
|
||||
SplitBinaryExpressionMethodCallQuickFix(desc, method, pickRightOperand = swapExpectedAndActual)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -7,6 +7,7 @@ import com.intellij.psi.PsiMethodCallExpression
|
||||
import com.intellij.psi.util.TypeConversionUtil
|
||||
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_BOOLEAN_ASSERT_CLASSNAME
|
||||
import de.platon42.intellij.plugins.cajon.MethodNames
|
||||
import de.platon42.intellij.plugins.cajon.calculateConstantParameterValue
|
||||
import de.platon42.intellij.plugins.cajon.firstArg
|
||||
import de.platon42.intellij.plugins.cajon.map
|
||||
|
||||
@@ -37,7 +38,7 @@ class AssertThatBooleanConditionInspection : AbstractAssertJInspection() {
|
||||
if (!TypeConversionUtil.isBooleanType(expectedExpression.type)) {
|
||||
return
|
||||
}
|
||||
val expectedResult = calculateConstantParameterValue(expression, 0) as? Boolean ?: return
|
||||
val expectedResult = expression.calculateConstantParameterValue(0) as? Boolean ?: return
|
||||
val flippedBooleanTest = matchingCalls.drop(2).any { it }
|
||||
|
||||
val replacementMethod = (expectedResult xor flippedBooleanTest).map(MethodNames.IS_TRUE, MethodNames.IS_FALSE)
|
||||
|
||||
+2
-1
@@ -6,6 +6,7 @@ import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.psi.PsiMethodCallExpression
|
||||
import com.intellij.psi.PsiStatement
|
||||
import de.platon42.intellij.plugins.cajon.MethodNames
|
||||
import de.platon42.intellij.plugins.cajon.calculateConstantParameterValue
|
||||
|
||||
class AssertThatEnumerableIsEmptyInspection : AbstractAssertJInspection() {
|
||||
|
||||
@@ -24,7 +25,7 @@ class AssertThatEnumerableIsEmptyInspection : AbstractAssertJInspection() {
|
||||
return
|
||||
}
|
||||
|
||||
val value = calculateConstantParameterValue(expression, 0) ?: return
|
||||
val value = expression.calculateConstantParameterValue(0) ?: return
|
||||
if (value == 0) {
|
||||
registerSimplifyMethod(holder, expression, MethodNames.IS_EMPTY)
|
||||
}
|
||||
|
||||
+64
-80
@@ -1,10 +1,8 @@
|
||||
package de.platon42.intellij.plugins.cajon.inspections
|
||||
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import com.intellij.psi.JavaElementVisitor
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.psi.PsiMethodCallExpression
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.siyeh.ig.callMatcher.CallMatcher
|
||||
import de.platon42.intellij.plugins.cajon.*
|
||||
@@ -15,97 +13,82 @@ class AssertThatGuavaOptionalInspection : AbstractAssertJInspection() {
|
||||
companion object {
|
||||
private const val DISPLAY_NAME = "Asserting an Optional (Guava)"
|
||||
private const val REPLACE_GUAVA_DESCRIPTION_TEMPLATE = "Replace %s() with Guava assertThat().%s()"
|
||||
private const val REMOVE_EXPECTED_OUTMOST_GUAVA_DESCRIPTION_TEMPLATE = "Remove unwrapping of expected expression and replace %s() with Guava assertThat().%s()"
|
||||
private const val REMOVE_ACTUAL_OUTMOST_GUAVA_DESCRIPTION_TEMPLATE = "Unwrap actual expression and replace %s() with Guava assertThat().%s()"
|
||||
}
|
||||
|
||||
override fun getDisplayName() = DISPLAY_NAME
|
||||
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
||||
return object : JavaElementVisitor() {
|
||||
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
|
||||
super.visitMethodCallExpression(expression)
|
||||
JavaPsiFacade.getInstance(expression.project)
|
||||
.findClass(AssertJClassNames.GUAVA_ASSERTIONS_CLASSNAME, GlobalSearchScope.allScope(expression.project)) ?: return
|
||||
val assertThatGuava = ASSERT_THAT_GUAVA_OPTIONAL.test(expression)
|
||||
if (!(ASSERT_THAT_ANY.test(expression) || assertThatGuava)) {
|
||||
override fun visitExpressionStatement(statement: PsiExpressionStatement?) {
|
||||
super.visitExpressionStatement(statement)
|
||||
val staticMethodCall = statement?.findStaticMethodCall() ?: return
|
||||
|
||||
if (!checkPreconditions(staticMethodCall)) {
|
||||
return
|
||||
}
|
||||
val expectedCallExpression = expression.findOutmostMethodCall() ?: return
|
||||
val actualExpression = staticMethodCall.firstArg as? PsiMethodCallExpression ?: return
|
||||
|
||||
val isEqualTo = IS_EQUAL_TO_OBJECT.test(expectedCallExpression)
|
||||
val isNotEqualTo = IS_NOT_EQUAL_TO_OBJECT.test(expectedCallExpression)
|
||||
if (assertThatGuava) {
|
||||
if (isEqualTo) {
|
||||
val innerExpectedCall = expectedCallExpression.firstArg as? PsiMethodCallExpression ?: return
|
||||
if (CallMatcher.anyOf(GUAVA_OPTIONAL_OF, GUAVA_OPTIONAL_FROM_NULLABLE).test(innerExpectedCall)) {
|
||||
registerRemoveExpectedOutmostMethod(holder, expression, expectedCallExpression, MethodNames.CONTAINS, ::UnwrapExpectedStaticMethodCallQuickFix)
|
||||
} else if (GUAVA_OPTIONAL_ABSENT.test(innerExpectedCall)) {
|
||||
registerSimplifyMethod(holder, expectedCallExpression, MethodNames.IS_ABSENT)
|
||||
}
|
||||
} else if (isNotEqualTo) {
|
||||
val innerExpectedCall = expectedCallExpression.firstArg as? PsiMethodCallExpression ?: return
|
||||
if (GUAVA_OPTIONAL_ABSENT.test(innerExpectedCall)) {
|
||||
registerSimplifyMethod(holder, expectedCallExpression, MethodNames.IS_PRESENT)
|
||||
val outmostMethodCall = statement.findOutmostMethodCall() ?: return
|
||||
if (GUAVA_OPTIONAL_GET.test(actualExpression)) {
|
||||
val expectedCallExpression = staticMethodCall.gatherAssertionCalls().singleOrNull() ?: return
|
||||
if (IS_EQUAL_TO_OBJECT.test(expectedCallExpression)) {
|
||||
registerMoveOutMethod(holder, outmostMethodCall, actualExpression, MethodNames.CONTAINS) { desc, method ->
|
||||
QuickFixWithPostfixDelegate(
|
||||
RemoveActualOutmostMethodCallQuickFix(desc, method),
|
||||
ForGuavaPostFix.REPLACE_BY_GUAVA_ASSERT_THAT_AND_STATIC_IMPORT
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// we're not calling an assertThat() from Guava, but a core-AssertJ one!
|
||||
// We need to replace that by the Guava one, if we want to apply a formally correct fix.
|
||||
val actualExpression = expression.firstArg as? PsiMethodCallExpression
|
||||
if (actualExpression != null) {
|
||||
if (GUAVA_OPTIONAL_GET.test(actualExpression) && isEqualTo) {
|
||||
registerRemoveActualOutmostForGuavaMethod(holder, expression, expectedCallExpression, MethodNames.CONTAINS)
|
||||
} else if (GUAVA_OPTIONAL_IS_PRESENT.test(actualExpression)) {
|
||||
val expectedPresence = getExpectedBooleanResult(expectedCallExpression) ?: return
|
||||
val replacementMethod = expectedPresence.map(MethodNames.IS_PRESENT, MethodNames.IS_ABSENT)
|
||||
registerRemoveActualOutmostForGuavaMethod(holder, expression, expectedCallExpression, replacementMethod, noExpectedExpression = true)
|
||||
}
|
||||
}
|
||||
if (isEqualTo) {
|
||||
val innerExpectedCall = expectedCallExpression.firstArg as? PsiMethodCallExpression ?: return
|
||||
if (CallMatcher.anyOf(GUAVA_OPTIONAL_OF, GUAVA_OPTIONAL_FROM_NULLABLE).test(innerExpectedCall)) {
|
||||
registerRemoveExpectedOutmostGuavaMethod(holder, expression, expectedCallExpression, MethodNames.CONTAINS)
|
||||
} else if (GUAVA_OPTIONAL_ABSENT.test(innerExpectedCall)) {
|
||||
registerSimplifyForGuavaMethod(holder, expectedCallExpression, MethodNames.IS_ABSENT)
|
||||
}
|
||||
} else if (isNotEqualTo) {
|
||||
val innerExpectedCall = expectedCallExpression.firstArg as? PsiMethodCallExpression ?: return
|
||||
if (GUAVA_OPTIONAL_ABSENT.test(innerExpectedCall)) {
|
||||
registerSimplifyForGuavaMethod(holder, expectedCallExpression, MethodNames.IS_PRESENT)
|
||||
}
|
||||
} else if (GUAVA_OPTIONAL_IS_PRESENT.test(actualExpression)) {
|
||||
val expectedPresence = outmostMethodCall.getAllTheSameExpectedBooleanConstants() ?: return
|
||||
val replacementMethod = expectedPresence.map(MethodNames.IS_PRESENT, MethodNames.IS_ABSENT)
|
||||
registerMoveOutMethod(holder, outmostMethodCall, actualExpression, replacementMethod) { desc, method ->
|
||||
QuickFixWithPostfixDelegate(
|
||||
MoveOutMethodCallExpressionQuickFix(desc, method),
|
||||
ForGuavaPostFix.REPLACE_BY_GUAVA_ASSERT_THAT_AND_STATIC_IMPORT
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerRemoveExpectedOutmostGuavaMethod(
|
||||
holder: ProblemsHolder,
|
||||
expression: PsiMethodCallExpression,
|
||||
oldExpectedCallExpression: PsiMethodCallExpression,
|
||||
replacementMethod: String
|
||||
) {
|
||||
registerConciseMethod(REMOVE_EXPECTED_OUTMOST_GUAVA_DESCRIPTION_TEMPLATE, holder, expression, oldExpectedCallExpression, replacementMethod) { desc, method ->
|
||||
QuickFixWithPostfixDelegate(
|
||||
UnwrapExpectedStaticMethodCallQuickFix(desc, method),
|
||||
ForGuavaPostFix.REPLACE_BY_GUAVA_ASSERT_THAT_AND_STATIC_IMPORT
|
||||
)
|
||||
}
|
||||
}
|
||||
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
|
||||
super.visitMethodCallExpression(expression)
|
||||
val staticMethodCall = expression.findStaticMethodCall() ?: return
|
||||
if (!checkPreconditions(staticMethodCall)) {
|
||||
return
|
||||
}
|
||||
// We're not calling an assertThat() from Guava, but a core-AssertJ one!
|
||||
// We need to replace that by the Guava one, if we want to apply a formally correct fix.
|
||||
if (IS_EQUAL_TO_OBJECT.test(expression)) {
|
||||
val innerExpectedCall = expression.firstArg as? PsiMethodCallExpression ?: return
|
||||
if (CallMatcher.anyOf(GUAVA_OPTIONAL_OF, GUAVA_OPTIONAL_FROM_NULLABLE).test(innerExpectedCall)) {
|
||||
registerRemoveExpectedOutmostMethod(holder, expression, expression, MethodNames.CONTAINS) { desc, method ->
|
||||
QuickFixWithPostfixDelegate(
|
||||
UnwrapExpectedStaticMethodCallQuickFix(desc, method),
|
||||
ForGuavaPostFix.REPLACE_BY_GUAVA_ASSERT_THAT_AND_STATIC_IMPORT
|
||||
)
|
||||
}
|
||||
} else if (GUAVA_OPTIONAL_ABSENT.test(innerExpectedCall)) {
|
||||
registerSimplifyForGuavaMethod(holder, expression, MethodNames.IS_ABSENT)
|
||||
}
|
||||
} else if (IS_NOT_EQUAL_TO_OBJECT.test(expression)) {
|
||||
val innerExpectedCall = expression.firstArg as? PsiMethodCallExpression ?: return
|
||||
if (GUAVA_OPTIONAL_ABSENT.test(innerExpectedCall)) {
|
||||
registerSimplifyForGuavaMethod(holder, expression, MethodNames.IS_PRESENT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerRemoveActualOutmostForGuavaMethod(
|
||||
holder: ProblemsHolder,
|
||||
expression: PsiMethodCallExpression,
|
||||
oldExpectedCallExpression: PsiMethodCallExpression,
|
||||
replacementMethod: String,
|
||||
noExpectedExpression: Boolean = false
|
||||
) {
|
||||
registerConciseMethod(REMOVE_ACTUAL_OUTMOST_GUAVA_DESCRIPTION_TEMPLATE, holder, expression, oldExpectedCallExpression, replacementMethod) { desc, method ->
|
||||
QuickFixWithPostfixDelegate(
|
||||
RemoveActualOutmostMethodCallQuickFix(desc, method, noExpectedExpression),
|
||||
ForGuavaPostFix.REPLACE_BY_GUAVA_ASSERT_THAT_AND_STATIC_IMPORT
|
||||
)
|
||||
private fun checkPreconditions(staticMethodCall: PsiMethodCallExpression): Boolean {
|
||||
val assertThatGuava = GUAVA_ASSERT_THAT_ANY.test(staticMethodCall)
|
||||
|
||||
if (ASSERT_THAT_ANY.test(staticMethodCall) || assertThatGuava) {
|
||||
JavaPsiFacade.getInstance(staticMethodCall.project)
|
||||
.findClass(AssertJClassNames.GUAVA_ASSERTIONS_CLASSNAME, GlobalSearchScope.allScope(staticMethodCall.project)) ?: return false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +100,7 @@ class AssertThatGuavaOptionalInspection : AbstractAssertJInspection() {
|
||||
ReplaceSimpleMethodCallQuickFix(description, replacementMethod),
|
||||
ForGuavaPostFix.REPLACE_BY_GUAVA_ASSERT_THAT_AND_STATIC_IMPORT
|
||||
)
|
||||
holder.registerProblem(expression, message, quickFix)
|
||||
val textRange = TextRange(expression.qualifierExpression.textLength, expression.textLength)
|
||||
holder.registerProblem(expression, textRange, message, quickFix)
|
||||
}
|
||||
}
|
||||
+14
-19
@@ -2,21 +2,15 @@ package de.platon42.intellij.plugins.cajon.inspections
|
||||
|
||||
import com.intellij.codeInspection.LocalQuickFix
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import com.intellij.psi.JavaElementVisitor
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.psi.PsiInstanceOfExpression
|
||||
import com.intellij.psi.PsiMethodCallExpression
|
||||
import de.platon42.intellij.plugins.cajon.MethodNames
|
||||
import de.platon42.intellij.plugins.cajon.findOutmostMethodCall
|
||||
import de.platon42.intellij.plugins.cajon.firstArg
|
||||
import de.platon42.intellij.plugins.cajon.map
|
||||
import de.platon42.intellij.plugins.cajon.quickfixes.RemoveInstanceOfExpressionQuickFix
|
||||
import com.intellij.psi.*
|
||||
import de.platon42.intellij.plugins.cajon.*
|
||||
import de.platon42.intellij.plugins.cajon.quickfixes.MoveOutInstanceOfExpressionQuickFix
|
||||
|
||||
class AssertThatInstanceOfInspection : AbstractAssertJInspection() {
|
||||
|
||||
companion object {
|
||||
private const val DISPLAY_NAME = "Asserting a class instance"
|
||||
private const val REPLACE_INSTANCEOF_DESCRIPTION_TEMPLATE = "Replace instanceof expression by assertThat().%s()"
|
||||
private const val REMOVE_INSTANCEOF_DESCRIPTION_TEMPLATE = "Remove instanceof in actual expression and use assertThat().%s() instead"
|
||||
private const val MOVE_OUT_INSTANCEOF_MESSAGE = "instanceof expression could be moved out of assertThat()"
|
||||
}
|
||||
|
||||
@@ -24,30 +18,31 @@ class AssertThatInstanceOfInspection : AbstractAssertJInspection() {
|
||||
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
||||
return object : JavaElementVisitor() {
|
||||
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
|
||||
super.visitMethodCallExpression(expression)
|
||||
if (!ASSERT_THAT_BOOLEAN.test(expression)) {
|
||||
override fun visitExpressionStatement(statement: PsiExpressionStatement?) {
|
||||
super.visitExpressionStatement(statement)
|
||||
val staticMethodCall = statement?.findStaticMethodCall() ?: return
|
||||
if (!ASSERT_THAT_BOOLEAN.test(staticMethodCall)) {
|
||||
return
|
||||
}
|
||||
|
||||
val expectedCallExpression = expression.findOutmostMethodCall() ?: return
|
||||
val expectedResult = getExpectedBooleanResult(expectedCallExpression) ?: return
|
||||
val expectedCallExpression = statement.findOutmostMethodCall() ?: return
|
||||
val expectedResult = expectedCallExpression.getAllTheSameExpectedBooleanConstants() ?: return
|
||||
|
||||
if (expression.firstArg is PsiInstanceOfExpression) {
|
||||
if (staticMethodCall.firstArg is PsiInstanceOfExpression) {
|
||||
val replacementMethod = expectedResult.map(MethodNames.IS_INSTANCE_OF, MethodNames.IS_NOT_INSTANCE_OF)
|
||||
registerRemoveInstanceOfMethod(holder, expression, replacementMethod, ::RemoveInstanceOfExpressionQuickFix)
|
||||
registerMoveOutInstanceOfMethod(holder, expectedCallExpression, replacementMethod, ::MoveOutInstanceOfExpressionQuickFix)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerRemoveInstanceOfMethod(
|
||||
private fun registerMoveOutInstanceOfMethod(
|
||||
holder: ProblemsHolder,
|
||||
expression: PsiMethodCallExpression,
|
||||
replacementMethod: String,
|
||||
quickFixSupplier: (String, String) -> LocalQuickFix
|
||||
) {
|
||||
val description = REPLACE_INSTANCEOF_DESCRIPTION_TEMPLATE.format(replacementMethod)
|
||||
val description = REMOVE_INSTANCEOF_DESCRIPTION_TEMPLATE.format(replacementMethod)
|
||||
val quickfix = quickFixSupplier(description, replacementMethod)
|
||||
holder.registerProblem(expression, MOVE_OUT_INSTANCEOF_MESSAGE, quickfix)
|
||||
}
|
||||
|
||||
+9
-22
@@ -1,19 +1,17 @@
|
||||
package de.platon42.intellij.plugins.cajon.inspections
|
||||
|
||||
import com.intellij.codeInspection.LocalQuickFix
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import com.intellij.psi.*
|
||||
import de.platon42.intellij.plugins.cajon.MethodNames
|
||||
import de.platon42.intellij.plugins.cajon.findOutmostMethodCall
|
||||
import de.platon42.intellij.plugins.cajon.findStaticMethodCall
|
||||
import de.platon42.intellij.plugins.cajon.firstArg
|
||||
import de.platon42.intellij.plugins.cajon.map
|
||||
import de.platon42.intellij.plugins.cajon.quickfixes.RemoveUnaryExpressionQuickFix
|
||||
import de.platon42.intellij.plugins.cajon.getExpectedBooleanResult
|
||||
import de.platon42.intellij.plugins.cajon.quickfixes.InvertUnaryStatementQuickFix
|
||||
|
||||
class AssertThatInvertedBooleanConditionInspection : AbstractAssertJInspection() {
|
||||
|
||||
companion object {
|
||||
private const val DISPLAY_NAME = "Asserting an inverted boolean condition"
|
||||
private const val INVERT_CONDITION_DESCRIPTION = "Invert condition in assertThat()"
|
||||
private const val INVERT_CONDITION_MESSAGE = "Condition inside assertThat() could be inverted"
|
||||
}
|
||||
|
||||
@@ -23,29 +21,18 @@ class AssertThatInvertedBooleanConditionInspection : AbstractAssertJInspection()
|
||||
return object : JavaElementVisitor() {
|
||||
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
|
||||
super.visitMethodCallExpression(expression)
|
||||
if (!ASSERT_THAT_BOOLEAN.test(expression)) {
|
||||
val staticMethodCall = expression.findStaticMethodCall() ?: return
|
||||
if (!ASSERT_THAT_BOOLEAN.test(staticMethodCall)) {
|
||||
return
|
||||
}
|
||||
expression.getExpectedBooleanResult() ?: return
|
||||
|
||||
val expectedCallExpression = expression.findOutmostMethodCall() ?: return
|
||||
val expectedResult = getExpectedBooleanResult(expectedCallExpression) ?: return
|
||||
|
||||
val prefixExpression = expression.firstArg as? PsiPrefixExpression ?: return
|
||||
val prefixExpression = staticMethodCall.firstArg as? PsiPrefixExpression ?: return
|
||||
if (prefixExpression.operationTokenType == JavaTokenType.EXCL) {
|
||||
val replacementMethod = expectedResult.map(MethodNames.IS_FALSE, MethodNames.IS_TRUE)
|
||||
registerInvertMethod(holder, expression, replacementMethod, ::RemoveUnaryExpressionQuickFix)
|
||||
val outmostMethodCall = expression.findOutmostMethodCall() ?: return
|
||||
holder.registerProblem(outmostMethodCall, INVERT_CONDITION_MESSAGE, InvertUnaryStatementQuickFix())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerInvertMethod(
|
||||
holder: ProblemsHolder,
|
||||
expression: PsiMethodCallExpression,
|
||||
replacementMethod: String,
|
||||
quickFixSupplier: (String, String) -> LocalQuickFix
|
||||
) {
|
||||
val quickfix = quickFixSupplier(INVERT_CONDITION_DESCRIPTION, replacementMethod)
|
||||
holder.registerProblem(expression, INVERT_CONDITION_MESSAGE, quickfix)
|
||||
}
|
||||
}
|
||||
+41
-36
@@ -3,12 +3,11 @@ package de.platon42.intellij.plugins.cajon.inspections
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import com.intellij.psi.JavaElementVisitor
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.psi.PsiExpressionStatement
|
||||
import com.intellij.psi.PsiMethodCallExpression
|
||||
import com.siyeh.ig.callMatcher.CallMatcher
|
||||
import de.platon42.intellij.plugins.cajon.MethodNames
|
||||
import de.platon42.intellij.plugins.cajon.findOutmostMethodCall
|
||||
import de.platon42.intellij.plugins.cajon.firstArg
|
||||
import de.platon42.intellij.plugins.cajon.map
|
||||
import de.platon42.intellij.plugins.cajon.*
|
||||
import de.platon42.intellij.plugins.cajon.quickfixes.MoveOutMethodCallExpressionQuickFix
|
||||
import de.platon42.intellij.plugins.cajon.quickfixes.RemoveActualOutmostMethodCallQuickFix
|
||||
import de.platon42.intellij.plugins.cajon.quickfixes.UnwrapExpectedStaticMethodCallQuickFix
|
||||
|
||||
@@ -22,46 +21,52 @@ class AssertThatJava8OptionalInspection : AbstractAssertJInspection() {
|
||||
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
||||
return object : JavaElementVisitor() {
|
||||
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
|
||||
super.visitMethodCallExpression(expression)
|
||||
if (!ASSERT_THAT_ANY.test(expression)) {
|
||||
override fun visitExpressionStatement(statement: PsiExpressionStatement?) {
|
||||
super.visitExpressionStatement(statement)
|
||||
val staticMethodCall = statement?.findStaticMethodCall() ?: return
|
||||
if (!ASSERT_THAT_ANY.test(staticMethodCall)) {
|
||||
return
|
||||
}
|
||||
val expectedCallExpression = expression.findOutmostMethodCall() ?: return
|
||||
val actualExpression = staticMethodCall.firstArg as? PsiMethodCallExpression ?: return
|
||||
|
||||
if (ASSERT_THAT_JAVA8_OPTIONAL.test(expression)) {
|
||||
val outmostMethodCall = statement.findOutmostMethodCall() ?: return
|
||||
if (OPTIONAL_GET.test(actualExpression)) {
|
||||
val expectedCallExpression = staticMethodCall.gatherAssertionCalls().singleOrNull() ?: return
|
||||
if (IS_EQUAL_TO_OBJECT.test(expectedCallExpression)) {
|
||||
val innerExpectedCall = expectedCallExpression.firstArg as? PsiMethodCallExpression ?: return
|
||||
if (CallMatcher.anyOf(OPTIONAL_OF, OPTIONAL_OF_NULLABLE).test(innerExpectedCall)) {
|
||||
registerRemoveExpectedOutmostMethod(holder, expression, expectedCallExpression, MethodNames.CONTAINS, ::UnwrapExpectedStaticMethodCallQuickFix)
|
||||
} else if (OPTIONAL_EMPTY.test(innerExpectedCall)) {
|
||||
registerSimplifyMethod(holder, expectedCallExpression, MethodNames.IS_NOT_PRESENT)
|
||||
registerMoveOutMethod(holder, outmostMethodCall, actualExpression, MethodNames.CONTAINS) { desc, method ->
|
||||
RemoveActualOutmostMethodCallQuickFix(desc, method)
|
||||
}
|
||||
} else if (IS_NOT_EQUAL_TO_OBJECT.test(expectedCallExpression)) {
|
||||
val innerExpectedCall = expectedCallExpression.firstArg as? PsiMethodCallExpression ?: return
|
||||
if (OPTIONAL_EMPTY.test(innerExpectedCall)) {
|
||||
registerSimplifyMethod(holder, expectedCallExpression, MethodNames.IS_PRESENT)
|
||||
} else if (IS_SAME_AS_OBJECT.test(expectedCallExpression)) {
|
||||
registerMoveOutMethod(holder, outmostMethodCall, actualExpression, MethodNames.CONTAINS_SAME) { desc, method ->
|
||||
RemoveActualOutmostMethodCallQuickFix(desc, method)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val actualExpression = expression.firstArg as? PsiMethodCallExpression ?: return
|
||||
} else if (OPTIONAL_IS_PRESENT.test(actualExpression)) {
|
||||
val expectedPresence = outmostMethodCall.getAllTheSameExpectedBooleanConstants() ?: return
|
||||
val replacementMethod = expectedPresence.map(MethodNames.IS_PRESENT, MethodNames.IS_NOT_PRESENT)
|
||||
registerMoveOutMethod(holder, outmostMethodCall, actualExpression, replacementMethod) { desc, method ->
|
||||
MoveOutMethodCallExpressionQuickFix(desc, method)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (OPTIONAL_GET.test(actualExpression)) {
|
||||
if (IS_EQUAL_TO_OBJECT.test(expectedCallExpression)) {
|
||||
registerRemoveActualOutmostMethod(holder, expression, expectedCallExpression, MethodNames.CONTAINS) { desc, method ->
|
||||
RemoveActualOutmostMethodCallQuickFix(desc, method)
|
||||
}
|
||||
} else if (IS_SAME_AS_OBJECT.test(expectedCallExpression)) {
|
||||
registerRemoveActualOutmostMethod(holder, expression, expectedCallExpression, MethodNames.CONTAINS_SAME) { desc, method ->
|
||||
RemoveActualOutmostMethodCallQuickFix(desc, method)
|
||||
}
|
||||
}
|
||||
} else if (OPTIONAL_IS_PRESENT.test(actualExpression)) {
|
||||
val expectedPresence = getExpectedBooleanResult(expectedCallExpression) ?: return
|
||||
val replacementMethod = expectedPresence.map(MethodNames.IS_PRESENT, MethodNames.IS_NOT_PRESENT)
|
||||
registerRemoveActualOutmostMethod(holder, expression, expectedCallExpression, replacementMethod) { desc, method ->
|
||||
RemoveActualOutmostMethodCallQuickFix(desc, method, noExpectedExpression = true)
|
||||
}
|
||||
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
|
||||
super.visitMethodCallExpression(expression)
|
||||
val staticMethodCall = expression.findStaticMethodCall() ?: return
|
||||
if (!ASSERT_THAT_JAVA8_OPTIONAL.test(staticMethodCall)) {
|
||||
return
|
||||
}
|
||||
if (IS_EQUAL_TO_OBJECT.test(expression)) {
|
||||
val innerExpectedCall = expression.firstArg as? PsiMethodCallExpression ?: return
|
||||
if (CallMatcher.anyOf(OPTIONAL_OF, OPTIONAL_OF_NULLABLE).test(innerExpectedCall)) {
|
||||
registerRemoveExpectedOutmostMethod(holder, expression, expression, MethodNames.CONTAINS, ::UnwrapExpectedStaticMethodCallQuickFix)
|
||||
} else if (OPTIONAL_EMPTY.test(innerExpectedCall)) {
|
||||
registerSimplifyMethod(holder, expression, MethodNames.IS_NOT_PRESENT)
|
||||
}
|
||||
} else if (IS_NOT_EQUAL_TO_OBJECT.test(expression)) {
|
||||
val innerExpectedCall = expression.firstArg as? PsiMethodCallExpression ?: return
|
||||
if (OPTIONAL_EMPTY.test(innerExpectedCall)) {
|
||||
registerSimplifyMethod(holder, expression, MethodNames.IS_PRESENT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+100
-66
@@ -2,10 +2,10 @@ package de.platon42.intellij.plugins.cajon.inspections
|
||||
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import de.platon42.intellij.plugins.cajon.*
|
||||
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_CHAR_SEQUENCE_ASSERT_CLASSNAME
|
||||
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_ITERABLE_ASSERT_CLASSNAME
|
||||
import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceHasSizeMethodCallQuickFix
|
||||
import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceSizeMethodCallQuickFix
|
||||
|
||||
class AssertThatSizeInspection : AbstractAssertJInspection() {
|
||||
@@ -13,6 +13,7 @@ class AssertThatSizeInspection : AbstractAssertJInspection() {
|
||||
companion object {
|
||||
private const val DISPLAY_NAME = "Asserting the size of an collection, array or string"
|
||||
private const val REMOVE_SIZE_DESCRIPTION_TEMPLATE = "Remove size determination of expected expression and replace %s() with %s()"
|
||||
private const val REMOVE_ALL_MESSAGE = "Try to operate on the iterable itself rather than its size"
|
||||
|
||||
private val BONUS_EXPRESSIONS_CALL_MATCHER_MAP = listOf(
|
||||
IS_LESS_THAN_INT to MethodNames.HAS_SIZE_LESS_THAN,
|
||||
@@ -20,90 +21,123 @@ class AssertThatSizeInspection : AbstractAssertJInspection() {
|
||||
IS_GREATER_THAN_INT to MethodNames.HAS_SIZE_GREATER_THAN,
|
||||
IS_GREATER_THAN_OR_EQUAL_TO_INT to MethodNames.HAS_SIZE_GREATER_THAN_OR_EQUAL_TO
|
||||
)
|
||||
|
||||
private fun isCharSequenceLength(expression: PsiExpression) = (expression is PsiMethodCallExpression) && CHAR_SEQUENCE_LENGTH.test(expression)
|
||||
|
||||
private fun isCollectionSize(expression: PsiExpression) = (expression is PsiMethodCallExpression) && COLLECTION_SIZE.test(expression)
|
||||
|
||||
private fun isArrayLength(expression: PsiExpression): Boolean {
|
||||
val psiReferenceExpression = expression as? PsiReferenceExpression ?: return false
|
||||
return ((psiReferenceExpression.qualifierExpression?.type is PsiArrayType)
|
||||
&& ((psiReferenceExpression.resolve() as? PsiField)?.name == "length"))
|
||||
}
|
||||
|
||||
fun getMatch(expression: PsiMethodCallExpression, isForArrayOrCollection: Boolean, isForString: Boolean): Match? {
|
||||
val isLastExpression = expression.parent is PsiStatement
|
||||
val constValue = expression.calculateConstantParameterValue(0)
|
||||
if (IS_EQUAL_TO_INT.test(expression)) {
|
||||
return if ((constValue == 0) && isLastExpression) {
|
||||
Match(expression, MethodNames.IS_EMPTY, noExpectedExpression = true)
|
||||
} else {
|
||||
val equalToExpression = expression.firstArg
|
||||
if (isForArrayOrCollection && (isCollectionSize(equalToExpression) || isArrayLength(equalToExpression)) ||
|
||||
isForString && (isCollectionSize(equalToExpression) || isArrayLength(equalToExpression) || isCharSequenceLength(equalToExpression))
|
||||
) {
|
||||
Match(expression, MethodNames.HAS_SAME_SIZE_AS, expectedIsCollection = true)
|
||||
} else {
|
||||
Match(expression, MethodNames.HAS_SIZE)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val isTestForEmpty = ((IS_LESS_THAN_OR_EQUAL_TO_INT.test(expression) && (constValue == 0))
|
||||
|| (IS_LESS_THAN_INT.test(expression) && (constValue == 1))
|
||||
|| IS_ZERO.test(expression))
|
||||
val isTestForNotEmpty = ((IS_GREATER_THAN_INT.test(expression) && (constValue == 0))
|
||||
|| (IS_GREATER_THAN_OR_EQUAL_TO_INT.test(expression) && (constValue == 1))
|
||||
|| IS_NOT_ZERO.test(expression))
|
||||
if ((isTestForEmpty && isLastExpression) || isTestForNotEmpty) {
|
||||
val replacementMethod = isTestForEmpty.map(MethodNames.IS_EMPTY, MethodNames.IS_NOT_EMPTY)
|
||||
return Match(expression, replacementMethod, noExpectedExpression = true)
|
||||
} else if (hasAssertJMethod(expression, ABSTRACT_ITERABLE_ASSERT_CLASSNAME, MethodNames.HAS_SIZE_LESS_THAN)) {
|
||||
// new stuff in AssertJ 13.2.0
|
||||
val replacementMethod = BONUS_EXPRESSIONS_CALL_MATCHER_MAP.find { it.first.test(expression) }?.second ?: return null
|
||||
return Match(expression, replacementMethod)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDisplayName() = DISPLAY_NAME
|
||||
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
||||
return object : JavaElementVisitor() {
|
||||
override fun visitExpressionStatement(statement: PsiExpressionStatement?) {
|
||||
super.visitExpressionStatement(statement)
|
||||
val staticMethodCall = statement?.findStaticMethodCall() ?: return
|
||||
if (!ASSERT_THAT_INT.test(staticMethodCall)) {
|
||||
return
|
||||
}
|
||||
val actualExpression = staticMethodCall.firstArg
|
||||
val isForArrayOrCollection = isArrayLength(actualExpression) || isCollectionSize(actualExpression)
|
||||
val isForString = isCharSequenceLength(actualExpression)
|
||||
if (!(isForArrayOrCollection || isForString)) {
|
||||
return
|
||||
}
|
||||
val matches = staticMethodCall.collectMethodCallsUpToStatement()
|
||||
.mapNotNull { getMatch(it, isForArrayOrCollection, isForString) }
|
||||
.toList()
|
||||
if (matches.isNotEmpty()) {
|
||||
if (matches.size == 1) {
|
||||
val match = matches.single()
|
||||
val expression = match.methodCall
|
||||
registerReplaceMethod(
|
||||
holder,
|
||||
expression,
|
||||
expression,
|
||||
match.replacementMethod
|
||||
)
|
||||
{ desc, method ->
|
||||
ReplaceSizeMethodCallQuickFix(desc, method, noExpectedExpression = match.noExpectedExpression, expectedIsCollection = match.expectedIsCollection)
|
||||
}
|
||||
} else {
|
||||
// I could try to create a quickfix for this, too, but it's probably not worth the effort
|
||||
holder.registerProblem(statement, REMOVE_ALL_MESSAGE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
|
||||
super.visitMethodCallExpression(expression)
|
||||
val isAssertThatWithInt = ASSERT_THAT_INT.test(expression)
|
||||
val isHasSize = HAS_SIZE.test(expression)
|
||||
if (!(isAssertThatWithInt || isHasSize)) {
|
||||
if (!(isHasSize)) {
|
||||
return
|
||||
}
|
||||
val actualExpression = expression.firstArg
|
||||
|
||||
val isForArrayOrCollection = isArrayLength(actualExpression) || isCollectionSize(actualExpression)
|
||||
val isForString = isCharSequenceLength(actualExpression)
|
||||
if (isHasSize && (isForArrayOrCollection
|
||||
if (!(isForArrayOrCollection
|
||||
|| (isForString && checkAssertedType(expression, ABSTRACT_CHAR_SEQUENCE_ASSERT_CLASSNAME)))
|
||||
) {
|
||||
val assertThatCall = PsiTreeUtil.findChildrenOfType(expression, PsiMethodCallExpression::class.java).find { CORE_ASSERT_THAT_MATCHER.test(it) } ?: return
|
||||
registerConciseMethod(
|
||||
REMOVE_SIZE_DESCRIPTION_TEMPLATE,
|
||||
holder,
|
||||
assertThatCall,
|
||||
expression,
|
||||
MethodNames.HAS_SAME_SIZE_AS
|
||||
) { desc, method ->
|
||||
ReplaceSizeMethodCallQuickFix(desc, method, expectedIsCollection = true, keepActualAsIs = true)
|
||||
}
|
||||
} else if (isForArrayOrCollection || isForString) {
|
||||
val expectedCallExpression = expression.findOutmostMethodCall() ?: return
|
||||
val constValue = calculateConstantParameterValue(expectedCallExpression, 0)
|
||||
if (IS_EQUAL_TO_INT.test(expectedCallExpression)) {
|
||||
if (constValue == 0) {
|
||||
registerReplaceMethod(holder, expression, expectedCallExpression, MethodNames.IS_EMPTY) { desc, method ->
|
||||
ReplaceSizeMethodCallQuickFix(desc, method, noExpectedExpression = true)
|
||||
}
|
||||
} else {
|
||||
val equalToExpression = expectedCallExpression.firstArg
|
||||
if (isForArrayOrCollection && (isCollectionSize(equalToExpression) || isArrayLength(equalToExpression)) ||
|
||||
isForString && (isCollectionSize(equalToExpression) || isArrayLength(equalToExpression) || isCharSequenceLength(equalToExpression))
|
||||
) {
|
||||
registerReplaceMethod(holder, expression, expectedCallExpression, MethodNames.HAS_SAME_SIZE_AS) { desc, method ->
|
||||
ReplaceSizeMethodCallQuickFix(desc, method, expectedIsCollection = true)
|
||||
}
|
||||
} else {
|
||||
registerReplaceMethod(holder, expression, expectedCallExpression, MethodNames.HAS_SIZE) { desc, method ->
|
||||
ReplaceSizeMethodCallQuickFix(desc, method)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val isTestForEmpty = ((IS_LESS_THAN_OR_EQUAL_TO_INT.test(expectedCallExpression) && (constValue == 0))
|
||||
|| (IS_LESS_THAN_INT.test(expectedCallExpression) && (constValue == 1))
|
||||
|| IS_ZERO.test(expectedCallExpression))
|
||||
val isTestForNotEmpty = ((IS_GREATER_THAN_INT.test(expectedCallExpression) && (constValue == 0))
|
||||
|| (IS_GREATER_THAN_OR_EQUAL_TO_INT.test(expectedCallExpression) && (constValue == 1))
|
||||
|| IS_NOT_ZERO.test(expectedCallExpression))
|
||||
if (isTestForEmpty || isTestForNotEmpty) {
|
||||
val replacementMethod = isTestForEmpty.map(MethodNames.IS_EMPTY, MethodNames.IS_NOT_EMPTY)
|
||||
registerReplaceMethod(holder, expression, expectedCallExpression, replacementMethod) { desc, method ->
|
||||
ReplaceSizeMethodCallQuickFix(desc, method, noExpectedExpression = true)
|
||||
}
|
||||
} else if (hasAssertJMethod(expression, ABSTRACT_ITERABLE_ASSERT_CLASSNAME, MethodNames.HAS_SIZE_LESS_THAN)) {
|
||||
// new stuff in AssertJ 13.2.0
|
||||
val matchedMethod = BONUS_EXPRESSIONS_CALL_MATCHER_MAP.find { it.first.test(expectedCallExpression) }?.second ?: return
|
||||
registerReplaceMethod(holder, expression, expectedCallExpression, matchedMethod) { desc, method ->
|
||||
ReplaceSizeMethodCallQuickFix(desc, method)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private fun isCharSequenceLength(expression: PsiExpression) = (expression is PsiMethodCallExpression) && CHAR_SEQUENCE_LENGTH.test(expression)
|
||||
|
||||
private fun isCollectionSize(expression: PsiExpression) = (expression is PsiMethodCallExpression) && COLLECTION_SIZE.test(expression)
|
||||
|
||||
private fun isArrayLength(expression: PsiExpression): Boolean {
|
||||
val psiReferenceExpression = expression as? PsiReferenceExpression ?: return false
|
||||
return ((psiReferenceExpression.qualifierExpression?.type is PsiArrayType)
|
||||
&& ((psiReferenceExpression.resolve() as? PsiField)?.name == "length"))
|
||||
registerConciseMethod(
|
||||
REMOVE_SIZE_DESCRIPTION_TEMPLATE,
|
||||
holder,
|
||||
expression,
|
||||
expression,
|
||||
MethodNames.HAS_SAME_SIZE_AS,
|
||||
::ReplaceHasSizeMethodCallQuickFix
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Match(
|
||||
val methodCall: PsiMethodCallExpression,
|
||||
val replacementMethod: String,
|
||||
val noExpectedExpression: Boolean = false,
|
||||
val expectedIsCollection: Boolean = false
|
||||
)
|
||||
}
|
||||
+13
-43
@@ -1,29 +1,20 @@
|
||||
package de.platon42.intellij.plugins.cajon.inspections
|
||||
|
||||
import com.intellij.codeInspection.LocalQuickFix
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import com.intellij.psi.CommonClassNames
|
||||
import com.intellij.psi.JavaElementVisitor
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.psi.PsiMethodCallExpression
|
||||
import com.intellij.psi.*
|
||||
import com.siyeh.ig.callMatcher.CallMatcher
|
||||
import de.platon42.intellij.plugins.cajon.MethodNames
|
||||
import de.platon42.intellij.plugins.cajon.findOutmostMethodCall
|
||||
import de.platon42.intellij.plugins.cajon.firstArg
|
||||
import de.platon42.intellij.plugins.cajon.quickfixes.MoveActualOuterExpressionMethodCallQuickFix
|
||||
import de.platon42.intellij.plugins.cajon.quickfixes.RemoveActualOutmostMethodCallQuickFix
|
||||
import de.platon42.intellij.plugins.cajon.*
|
||||
import de.platon42.intellij.plugins.cajon.quickfixes.MoveOutMethodCallExpressionQuickFix
|
||||
|
||||
class AssertThatStringExpressionInspection : AbstractAssertJInspection() {
|
||||
|
||||
companion object {
|
||||
private const val DISPLAY_NAME = "Asserting a string specific expression"
|
||||
private const val MOVE_EXPECTED_EXPRESSION_DESCRIPTION_TEMPLATE = "Remove %s() of expected expression and use assertThat().%s() instead"
|
||||
private const val MOVING_OUT_MESSAGE_TEMPLATE = "Moving %s() expression out of assertThat() would be more concise"
|
||||
|
||||
private val MAPPINGS = listOf(
|
||||
Mapping(
|
||||
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "isEmpty").parameterCount(0)!!,
|
||||
MethodNames.IS_EMPTY, MethodNames.IS_NOT_EMPTY, hasExpected = false
|
||||
MethodNames.IS_EMPTY, MethodNames.IS_NOT_EMPTY
|
||||
),
|
||||
Mapping(
|
||||
CallMatcher.anyOf(
|
||||
@@ -55,48 +46,27 @@ class AssertThatStringExpressionInspection : AbstractAssertJInspection() {
|
||||
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
||||
return object : JavaElementVisitor() {
|
||||
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
|
||||
super.visitMethodCallExpression(expression)
|
||||
if (!ASSERT_THAT_BOOLEAN.test(expression)) {
|
||||
override fun visitExpressionStatement(statement: PsiExpressionStatement?) {
|
||||
super.visitExpressionStatement(statement)
|
||||
val staticMethodCall = statement?.findStaticMethodCall() ?: return
|
||||
if (!ASSERT_THAT_BOOLEAN.test(staticMethodCall)) {
|
||||
return
|
||||
}
|
||||
val assertThatArgument = expression.firstArg as? PsiMethodCallExpression ?: return
|
||||
|
||||
val assertThatArgument = staticMethodCall.firstArg as? PsiMethodCallExpression ?: return
|
||||
val mapping = MAPPINGS.firstOrNull { it.callMatcher.test(assertThatArgument) } ?: return
|
||||
|
||||
val expectedCallExpression = expression.findOutmostMethodCall() ?: return
|
||||
val expectedResult = getExpectedBooleanResult(expectedCallExpression) ?: return
|
||||
val expectedCallExpression = statement.findOutmostMethodCall() ?: return
|
||||
val expectedResult = expectedCallExpression.getAllTheSameExpectedBooleanConstants() ?: return
|
||||
|
||||
val replacementMethod = if (expectedResult) mapping.replacementForTrue else mapping.replacementForFalse
|
||||
if (mapping.hasExpected) {
|
||||
registerMoveOutMethod(holder, expression, assertThatArgument, replacementMethod, ::MoveActualOuterExpressionMethodCallQuickFix)
|
||||
} else {
|
||||
registerMoveOutMethod(holder, expression, assertThatArgument, replacementMethod) { desc, method ->
|
||||
RemoveActualOutmostMethodCallQuickFix(desc, method, noExpectedExpression = true)
|
||||
}
|
||||
}
|
||||
registerMoveOutMethod(holder, expectedCallExpression, assertThatArgument, replacementMethod, ::MoveOutMethodCallExpressionQuickFix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerMoveOutMethod(
|
||||
holder: ProblemsHolder,
|
||||
expression: PsiMethodCallExpression,
|
||||
oldActualExpression: PsiMethodCallExpression,
|
||||
replacementMethod: String,
|
||||
quickFixSupplier: (String, String) -> LocalQuickFix
|
||||
) {
|
||||
val originalMethod = getOriginalMethodName(oldActualExpression) ?: return
|
||||
val description = MOVE_EXPECTED_EXPRESSION_DESCRIPTION_TEMPLATE.format(originalMethod, replacementMethod)
|
||||
val message = MOVING_OUT_MESSAGE_TEMPLATE.format(originalMethod)
|
||||
val quickfix = quickFixSupplier(description, replacementMethod)
|
||||
holder.registerProblem(expression, message, quickfix)
|
||||
}
|
||||
|
||||
private class Mapping(
|
||||
val callMatcher: CallMatcher,
|
||||
val replacementForTrue: String,
|
||||
val replacementForFalse: String,
|
||||
val hasExpected: Boolean = true
|
||||
val replacementForFalse: String
|
||||
)
|
||||
}
|
||||
+2
-1
@@ -7,6 +7,7 @@ import com.intellij.psi.PsiMethodCallExpression
|
||||
import com.intellij.psi.PsiStatement
|
||||
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_CHAR_SEQUENCE_ASSERT_CLASSNAME
|
||||
import de.platon42.intellij.plugins.cajon.MethodNames
|
||||
import de.platon42.intellij.plugins.cajon.calculateConstantParameterValue
|
||||
|
||||
class AssertThatStringIsEmptyInspection : AbstractAssertJInspection() {
|
||||
|
||||
@@ -31,7 +32,7 @@ class AssertThatStringIsEmptyInspection : AbstractAssertJInspection() {
|
||||
return
|
||||
}
|
||||
|
||||
val value = calculateConstantParameterValue(expression, 0) ?: return
|
||||
val value = expression.calculateConstantParameterValue(0) ?: return
|
||||
if ((isEqual && (value == "")) || (hasSize && (value == 0))) {
|
||||
registerSimplifyMethod(holder, expression, MethodNames.IS_EMPTY)
|
||||
}
|
||||
|
||||
+3
-1
@@ -16,6 +16,8 @@ class JUnitAssertToAssertJInspection : AbstractJUnitAssertInspection() {
|
||||
|
||||
companion object {
|
||||
private const val DISPLAY_NAME = "Convert JUnit assertions to AssertJ"
|
||||
private const val CONVERT_MESSAGE_TEMPLATE = "%s can be converted to AssertJ style"
|
||||
private const val CONVERT_DESCRIPTION_TEMPLATE = "Convert %s() to assertThat().%s()"
|
||||
|
||||
private val MAPPINGS = listOf(
|
||||
Mapping(
|
||||
@@ -142,7 +144,7 @@ class JUnitAssertToAssertJInspection : AbstractJUnitAssertInspection() {
|
||||
quickFixSupplier: (String, String) -> LocalQuickFix
|
||||
) {
|
||||
val originalMethod = getOriginalMethodName(expression) ?: return
|
||||
val description = REPLACE_DESCRIPTION_TEMPLATE.format(originalMethod, replacementMethod)
|
||||
val description = CONVERT_DESCRIPTION_TEMPLATE.format(originalMethod, replacementMethod)
|
||||
val message = CONVERT_MESSAGE_TEMPLATE.format(originalMethod)
|
||||
val quickfix = quickFixSupplier(description, replacementMethod)
|
||||
holder.registerProblem(expression, message, quickfix)
|
||||
|
||||
+5
-1
@@ -37,7 +37,11 @@ class JoinAssertThatStatementsInspection : AbstractAssertJInspection() {
|
||||
if (!reset) {
|
||||
val isSame = when (actualExpression) {
|
||||
is PsiMethodCallExpression -> equivalenceChecker.expressionsAreEquivalent(actualExpression, lastActualExpression)
|
||||
&& !KNOWN_METHODS_WITH_SIDE_EFFECTS.test(actualExpression)
|
||||
&& PsiTreeUtil.findChildrenOfAnyType(
|
||||
actualExpression,
|
||||
false,
|
||||
PsiMethodCallExpression::class.java
|
||||
).none { KNOWN_METHODS_WITH_SIDE_EFFECTS.test(it) }
|
||||
else -> equivalenceChecker.expressionsAreEquivalent(actualExpression, lastActualExpression)
|
||||
}
|
||||
if (isSame) {
|
||||
|
||||
Reference in New Issue
Block a user