Implemented AssertThatSizeInspection.
This commit is contained in:
+45
@@ -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)
|
||||
|
||||
+90
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -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)
|
||||
}
|
||||
}
|
||||
+44
@@ -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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user