Implemented AssertThatSizeInspection.

This commit is contained in:
2019-03-31 20:44:52 +02:00
parent 847e46c217
commit 8a246c1319
13 changed files with 315 additions and 61 deletions
@@ -21,24 +21,48 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
const val REPLACE_DESCRIPTION_TEMPLATE = "Replace %s with %s"
@NonNls
const val ASSERTIONS_CLASSNAME = "org.assertj.core.api.Assertions"
@NonNls
const val ABSTRACT_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractAssert"
@NonNls
const val ABSTRACT_BOOLEAN_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractBooleanAssert"
@NonNls
const val ABSTRACT_INTEGER_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractIntegerAssert"
@NonNls
const val ABSTRACT_COMPARABLE_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractComparableAssert"
@NonNls
const val ABSTRACT_STRING_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractStringAssert"
@NonNls
const val ABSTRACT_CHAR_SEQUENCE_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractCharSequenceAssert"
@NonNls
const val ABSTRACT_ENUMERABLE_ASSERT_CLASSNAME = "org.assertj.core.api.EnumerableAssert"
@NonNls
const val ASSERT_THAT_METHOD = "assertThat"
@NonNls
const val IS_EQUAL_TO_METHOD = "isEqualTo"
@NonNls
const val IS_NOT_EQUAL_TO_METHOD = "isNotEqualTo"
@NonNls
const val IS_GREATER_THAN_METHOD = "isGreaterThan"
@NonNls
const val IS_GREATER_THAN_OR_EQUAL_TO_METHOD = "isGreaterThanOrEqualTo"
@NonNls
const val IS_LESS_THAN_METHOD = "isLessThan"
@NonNls
const val IS_LESS_THAN_OR_EQUAL_TO_METHOD = "isLessThanOrEqualTo"
@NonNls
const val IS_ZERO_METHOD = "isZero"
@NonNls
const val IS_NOT_ZERO_METHOD = "isNotZero"
@NonNls
const val HAS_SIZE_METHOD = "hasSize"
val ASSERT_THAT_INT = CallMatcher.staticCall(ASSERTIONS_CLASSNAME, ASSERT_THAT_METHOD)
.parameterTypes("int")!!
val IS_EQUAL_TO_OBJECT = CallMatcher.instanceCall(ABSTRACT_ASSERT_CLASSNAME, IS_EQUAL_TO_METHOD)
.parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!!
val IS_NOT_EQUAL_TO_OBJECT = CallMatcher.instanceCall(ABSTRACT_ASSERT_CLASSNAME, IS_NOT_EQUAL_TO_METHOD)
@@ -50,6 +74,26 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
.parameterTypes("boolean")!!
val HAS_SIZE = CallMatcher.instanceCall(ABSTRACT_ENUMERABLE_ASSERT_CLASSNAME, HAS_SIZE_METHOD)
.parameterTypes("int")!!
val IS_EQUAL_TO_INT = CallMatcher.instanceCall(ABSTRACT_ASSERT_CLASSNAME, IS_EQUAL_TO_METHOD)
.parameterTypes("int")!!
val IS_GREATER_THAN_INT = CallMatcher.instanceCall(ABSTRACT_COMPARABLE_ASSERT_CLASSNAME, IS_GREATER_THAN_METHOD)
.parameterTypes("int")!!
val IS_GREATER_THAN_OR_EQUAL_TO_INT = CallMatcher.instanceCall(ABSTRACT_COMPARABLE_ASSERT_CLASSNAME, IS_GREATER_THAN_OR_EQUAL_TO_METHOD)
.parameterTypes("int")!!
val IS_LESS_THAN_INT = CallMatcher.instanceCall(ABSTRACT_COMPARABLE_ASSERT_CLASSNAME, IS_LESS_THAN_METHOD)
.parameterTypes("int")!!
val IS_LESS_THAN_OR_EQUAL_TO_INT = CallMatcher.instanceCall(ABSTRACT_COMPARABLE_ASSERT_CLASSNAME, IS_LESS_THAN_OR_EQUAL_TO_METHOD)
.parameterTypes("int")!!
val IS_ZERO = CallMatcher.instanceCall(ABSTRACT_INTEGER_ASSERT_CLASSNAME, IS_ZERO_METHOD)
.parameterCount(0)!!
val IS_NOT_ZERO = CallMatcher.instanceCall(ABSTRACT_INTEGER_ASSERT_CLASSNAME, IS_NOT_ZERO_METHOD)
.parameterCount(0)!!
val COLLECTION_SIZE = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_COLLECTION, "size")
.parameterCount(0)!!
}
override fun getGroupDisplayName(): String {
@@ -88,6 +132,7 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
}
protected fun calculateConstantParameterValue(expression: PsiMethodCallExpression, argIndex: Int): Any? {
if (argIndex >= expression.argumentList.expressionCount) return null
val valueExpression = expression.argumentList.expressions[argIndex] ?: return null
val constantEvaluationHelper = JavaPsiFacade.getInstance(expression.project).constantEvaluationHelper
return constantEvaluationHelper.computeConstantExpression(valueExpression)
@@ -0,0 +1,90 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.util.TextRange
import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil
import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceSizeMethodCallQuickFix
class AssertThatSizeInspection : AbstractAssertJInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting the size of an collection or array"
private const val CONCISER_MESSAGE_TEMPLATE = "%s would be conciser than %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)
if (!ASSERT_THAT_INT.test(expression)) {
return
}
val actualExpression = expression.argumentList.expressions[0] ?: return
if (isArrayLength(actualExpression) || isCollectionSize(actualExpression)) {
val statement = PsiTreeUtil.getParentOfType(expression, PsiStatement::class.java) ?: return
val expectedCallExpression = PsiTreeUtil.findChildOfType(statement, PsiMethodCallExpression::class.java) ?: return
val constValue = calculateConstantParameterValue(expectedCallExpression, 0)
if (IS_EQUAL_TO_INT.test(expectedCallExpression)) {
if (constValue == 0) {
registerSizeMethod(holder, expression, "isEmpty()", noExpectedExpression = true)
return
}
val equalToExpression = expectedCallExpression.argumentList.expressions[0]
if (isCollectionSize(equalToExpression) || isArrayLength(equalToExpression)) {
registerSizeMethod(holder, expression, "hasSameSizeAs()", expectedIsCollection = true)
return
}
registerSizeMethod(holder, expression, "hasSize()")
} else {
if ((IS_LESS_THAN_OR_EQUAL_TO_INT.test(expectedCallExpression) && (constValue == 0))
|| (IS_LESS_THAN_INT.test(expectedCallExpression) && (constValue == 1))
|| IS_ZERO.test(expectedCallExpression)
) {
registerSizeMethod(holder, expression, "isEmpty()", noExpectedExpression = true)
return
}
if ((IS_GREATER_THAN_INT.test(expectedCallExpression) && (constValue == 0))
|| (IS_GREATER_THAN_OR_EQUAL_TO_INT.test(expectedCallExpression) && (constValue == 1))
|| IS_NOT_ZERO.test(expectedCallExpression)
) {
registerSizeMethod(holder, expression, "isNotEmpty()", noExpectedExpression = true)
return
}
}
}
}
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"))
}
private fun registerSizeMethod(
holder: ProblemsHolder,
expression: PsiMethodCallExpression,
replacementMethod: String,
noExpectedExpression: Boolean = false,
expectedIsCollection: Boolean = false
) {
val originalMethod = getOriginalMethodName(expression) ?: return
val description = REPLACE_DESCRIPTION_TEMPLATE.format(replacementMethod, originalMethod)
val message = CONCISER_MESSAGE_TEMPLATE.format(originalMethod, replacementMethod)
holder.registerProblem(
expression,
message,
ProblemHighlightType.INFORMATION,
null as TextRange?,
ReplaceSizeMethodCallQuickFix(description, replacementMethod, noExpectedExpression, expectedIsCollection)
)
}
}
}
}
@@ -12,9 +12,9 @@ class ReplaceSimpleMethodCallQuickFix(description: String, private val replaceme
val factory = JavaPsiFacade.getElementFactory(element.project)
val methodCallExpression = element as? PsiMethodCallExpression ?: return
val oldQualifier = methodCallExpression.methodExpression.qualifierExpression ?: return
val isEmptyExpression =
val expectedExpression =
factory.createExpressionFromText("a.$replacementMethod", element) as PsiMethodCallExpression
isEmptyExpression.methodExpression.qualifierExpression!!.replace(oldQualifier)
element.replace(isEmptyExpression)
expectedExpression.methodExpression.qualifierExpression!!.replace(oldQualifier)
element.replace(expectedExpression)
}
}
@@ -0,0 +1,44 @@
package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil
class ReplaceSizeMethodCallQuickFix(
description: String,
private val replacementMethod: String,
private val noExpectedExpression: Boolean,
private val expectedIsCollection: Boolean
) : AbstractCommonQuickFix(description) {
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.startElement
val factory = JavaPsiFacade.getElementFactory(element.project)
val methodCallExpression = element as? PsiMethodCallExpression ?: return
val assertExpression = methodCallExpression.argumentList.expressions[0] ?: return
replaceCollectionSizeOrArrayLength(assertExpression)
val statement = PsiTreeUtil.getParentOfType(element, PsiStatement::class.java) ?: return
val oldExpectedExpression = PsiTreeUtil.findChildOfType(statement, PsiMethodCallExpression::class.java) ?: return
val expectedExpression =
factory.createExpressionFromText("a.${if (noExpectedExpression) replacementMethod else replacementMethod.replace("()", "(e)")}", element) as PsiMethodCallExpression
if (!noExpectedExpression) {
if (expectedIsCollection) {
replaceCollectionSizeOrArrayLength(oldExpectedExpression.argumentList.expressions[0])
}
expectedExpression.argumentList.expressions[0].replace(oldExpectedExpression.argumentList.expressions[0])
}
expectedExpression.methodExpression.qualifierExpression!!.replace(oldExpectedExpression.methodExpression.qualifierExpression!!)
oldExpectedExpression.replace(expectedExpression)
}
private fun replaceCollectionSizeOrArrayLength(assertExpression: PsiExpression) {
assertExpression.replace(
when (assertExpression) {
is PsiReferenceExpression -> assertExpression.qualifierExpression!!
is PsiMethodCallExpression -> assertExpression.methodExpression.qualifierExpression!!
else -> return
}
)
}
}