Implemented first version of JoinAssertThatStatements inspection that will try to merge assertThat() statements with the same actual object together, preserving comments.
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
package de.platon42.intellij.plugins.cajon
|
||||
|
||||
import com.intellij.psi.CommonClassNames
|
||||
import com.siyeh.ig.callMatcher.CallMatcher
|
||||
|
||||
val CORE_ASSERT_THAT_MATCHER = CallMatcher.staticCall(AssertJClassNames.ASSERTIONS_CLASSNAME, MethodNames.ASSERT_THAT)!!
|
||||
val GUAVA_ASSERT_THAT_MATCHER = CallMatcher.staticCall(AssertJClassNames.GUAVA_ASSERTIONS_CLASSNAME, MethodNames.ASSERT_THAT)!!
|
||||
val ALL_ASSERT_THAT_MATCHERS = CallMatcher.anyOf(CORE_ASSERT_THAT_MATCHER, GUAVA_ASSERT_THAT_MATCHER)!!
|
||||
val EXTRACTING_FROM_OBJECT = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_OBJECT_ASSERT_CLASSNAME, "extracting")!!
|
||||
val EXTRACTING_FROM_ITERABLE = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_ITERABLE_ASSERT_CLASSNAME, "extracting")!!
|
||||
val FLAT_EXTRACTING_FROM_ITERABLE = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_ITERABLE_ASSERT_CLASSNAME, "flatExtracting")!!
|
||||
val EXTRACTING_RESULT_OF_FROM_ITERABLE = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_ITERABLE_ASSERT_CLASSNAME, "extractingResultOf")!!
|
||||
|
||||
val EXTRACTING_CALL_MATCHERS = CallMatcher.anyOf(
|
||||
EXTRACTING_FROM_OBJECT,
|
||||
EXTRACTING_FROM_ITERABLE,
|
||||
FLAT_EXTRACTING_FROM_ITERABLE,
|
||||
EXTRACTING_RESULT_OF_FROM_ITERABLE
|
||||
)!!
|
||||
|
||||
val KNOWN_METHODS_WITH_SIDE_EFFECTS = CallMatcher.anyOf(
|
||||
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_ITERATOR, "next")
|
||||
)!!
|
||||
@@ -4,6 +4,7 @@ import com.intellij.psi.*
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.siyeh.ig.callMatcher.CallMatcher
|
||||
|
||||
val PsiMethodCallExpression.qualifierExpression: PsiExpression get() = this.methodExpression.qualifierExpression!!
|
||||
val PsiMethodCallExpression.firstArg: PsiExpression get() = this.argumentList.expressions[0]!!
|
||||
@@ -21,6 +22,17 @@ fun PsiElement.findOutmostMethodCall(): PsiMethodCallExpression? {
|
||||
return PsiTreeUtil.findChildOfType(statement, PsiMethodCallExpression::class.java)
|
||||
}
|
||||
|
||||
fun PsiMethodCallExpression.findFluentCallTo(matcher: CallMatcher): PsiMethodCallExpression? {
|
||||
var currentMethodCall: PsiMethodCallExpression? = this
|
||||
while (currentMethodCall != null) {
|
||||
if (matcher.test(currentMethodCall)) {
|
||||
return currentMethodCall
|
||||
}
|
||||
currentMethodCall = PsiTreeUtil.getParentOfType(currentMethodCall, PsiMethodCallExpression::class.java, true, PsiStatement::class.java)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun PsiMethodCallExpression.getArg(n: Int): PsiExpression = this.argumentList.expressions[n]
|
||||
|
||||
fun <T> Boolean.map(forTrue: T, forFalse: T) = if (this) forTrue else forFalse
|
||||
|
||||
@@ -4,9 +4,6 @@ import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiExpression
|
||||
import com.intellij.psi.PsiMethodCallExpression
|
||||
import com.siyeh.ig.callMatcher.CallMatcher
|
||||
|
||||
val CORE_ASSERT_THAT_MATCHER = CallMatcher.staticCall(AssertJClassNames.ASSERTIONS_CLASSNAME, MethodNames.ASSERT_THAT)!!
|
||||
|
||||
fun createAssertThat(context: PsiElement, actualExpression: PsiExpression): PsiMethodCallExpression {
|
||||
return createAssertThat(context, AssertJClassNames.ASSERTIONS_CLASSNAME, actualExpression)
|
||||
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package de.platon42.intellij.plugins.cajon.inspections
|
||||
|
||||
import com.intellij.codeInspection.ProblemHighlightType
|
||||
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.quickfixes.JoinStatementsQuickFix
|
||||
|
||||
class JoinAssertThatStatementsInspection : AbstractAssertJInspection() {
|
||||
|
||||
companion object {
|
||||
private const val DISPLAY_NAME = "Joining multiple assertThat() statements with same actual expression"
|
||||
private const val CAN_BE_JOINED_DESCRIPTION = "Multiple assertThat() statements can be joined together"
|
||||
}
|
||||
|
||||
override fun getDisplayName() = DISPLAY_NAME
|
||||
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
||||
return object : JavaElementVisitor() {
|
||||
override fun visitCodeBlock(block: PsiCodeBlock?) {
|
||||
super.visitCodeBlock(block)
|
||||
val statements = block?.statements ?: return
|
||||
var lastActualExpression: PsiExpression? = null
|
||||
var sameCount = 0
|
||||
var firstStatement: PsiStatement? = null
|
||||
var lastStatement: PsiStatement? = null
|
||||
for (statement in statements) {
|
||||
val assertThatCall = isLegitAssertThatCall(statement)
|
||||
var reset = true
|
||||
var actualExpression: PsiExpression? = null
|
||||
if (assertThatCall != null) {
|
||||
reset = (lastActualExpression == null)
|
||||
actualExpression = assertThatCall.firstArg
|
||||
if (!reset) {
|
||||
val isSame = when (actualExpression) {
|
||||
is PsiReferenceExpression -> (actualExpression.qualifierExpression == (lastActualExpression as? PsiReferenceExpression)?.qualifierExpression)
|
||||
is PsiMethodCallExpression -> (actualExpression.text == (lastActualExpression as? PsiMethodCallExpression)?.text)
|
||||
&& !KNOWN_METHODS_WITH_SIDE_EFFECTS.test(actualExpression)
|
||||
is PsiPolyadicExpression -> (actualExpression.text == (lastActualExpression as? PsiPolyadicExpression)?.text)
|
||||
else -> false
|
||||
}
|
||||
if (isSame) {
|
||||
sameCount++
|
||||
lastStatement = statement
|
||||
} else {
|
||||
reset = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if (reset) {
|
||||
if (sameCount > 1) {
|
||||
registerProblem(firstStatement, lastStatement)
|
||||
}
|
||||
firstStatement = statement
|
||||
lastStatement = null
|
||||
lastActualExpression = actualExpression
|
||||
sameCount = 1
|
||||
}
|
||||
}
|
||||
if (sameCount > 1) {
|
||||
registerProblem(firstStatement, lastStatement)
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerProblem(firstStatement: PsiStatement?, lastStatement: PsiStatement?) {
|
||||
val problemDescriptor = holder.manager.createProblemDescriptor(
|
||||
firstStatement!!,
|
||||
lastStatement!!,
|
||||
CAN_BE_JOINED_DESCRIPTION,
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
|
||||
isOnTheFly,
|
||||
JoinStatementsQuickFix()
|
||||
)
|
||||
holder.registerProblem(problemDescriptor)
|
||||
}
|
||||
|
||||
private fun isLegitAssertThatCall(statement: PsiStatement?): PsiMethodCallExpression? {
|
||||
if ((statement is PsiExpressionStatement) && (statement.expression is PsiMethodCallExpression)) {
|
||||
val assertThatCall = PsiTreeUtil.findChildrenOfType(statement, PsiMethodCallExpression::class.java).find { ALL_ASSERT_THAT_MATCHERS.test(it) }
|
||||
return assertThatCall?.takeIf { it.findFluentCallTo(EXTRACTING_CALL_MATCHERS) == null }
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
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.codeStyle.CodeStyleManager
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import de.platon42.intellij.plugins.cajon.ALL_ASSERT_THAT_MATCHERS
|
||||
|
||||
class JoinStatementsQuickFix : AbstractCommonQuickFix(JOIN_STATEMENTS_MESSAGE) {
|
||||
companion object {
|
||||
private const val JOIN_STATEMENTS_MESSAGE = "Join assertThat() statements"
|
||||
}
|
||||
|
||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||
val firstStatement = descriptor.startElement as PsiExpressionStatement
|
||||
val lastStatement = descriptor.endElement as PsiExpressionStatement
|
||||
do {
|
||||
val commentsToKeep = ArrayList<PsiComment>()
|
||||
val stuffToDelete = ArrayList<PsiElement>()
|
||||
var previousStatement = lastStatement.prevSibling ?: throw IllegalStateException("Internal error")
|
||||
while (previousStatement !is PsiExpressionStatement) {
|
||||
if (previousStatement is PsiComment) {
|
||||
commentsToKeep.add(previousStatement.copy() as PsiComment)
|
||||
}
|
||||
stuffToDelete.add(previousStatement)
|
||||
previousStatement = previousStatement.prevSibling ?: throw IllegalStateException("Internal error")
|
||||
}
|
||||
stuffToDelete.forEach { if (it.isValid) it.delete() }
|
||||
|
||||
val statementComments = PsiTreeUtil.getChildrenOfAnyType(previousStatement, PsiComment::class.java)
|
||||
commentsToKeep.addAll(statementComments)
|
||||
|
||||
val assertThatCallOfCursorStatement =
|
||||
PsiTreeUtil.findChildrenOfType(lastStatement, PsiMethodCallExpression::class.java).find { ALL_ASSERT_THAT_MATCHERS.test(it) }
|
||||
?: throw IllegalStateException("Internal error")
|
||||
|
||||
val lastElementBeforeConcat = assertThatCallOfCursorStatement.parent
|
||||
commentsToKeep.forEach {
|
||||
lastElementBeforeConcat.addAfter(it, lastElementBeforeConcat.firstChild)
|
||||
val newLineNode =
|
||||
PsiParserFacade.SERVICE.getInstance(project).createWhiteSpaceFromText("\n\t")
|
||||
|
||||
lastElementBeforeConcat.addAfter(newLineNode, lastElementBeforeConcat.firstChild)
|
||||
}
|
||||
|
||||
val newLeaf = previousStatement.firstChild
|
||||
assertThatCallOfCursorStatement.replace(newLeaf)
|
||||
previousStatement.delete()
|
||||
} while (previousStatement !== firstStatement)
|
||||
val codeBlock = PsiTreeUtil.getParentOfType(lastStatement, PsiCodeBlock::class.java) ?: return
|
||||
CodeStyleManager.getInstance(project).reformat(codeBlock)
|
||||
}
|
||||
}
|
||||
+2
-6
@@ -5,7 +5,7 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.psi.PsiInstanceOfExpression
|
||||
import com.intellij.psi.PsiMethodCallExpression
|
||||
import com.intellij.psi.PsiParenthesizedExpression
|
||||
import com.intellij.psi.util.PsiUtil
|
||||
import de.platon42.intellij.plugins.cajon.createExpectedMethodCall
|
||||
import de.platon42.intellij.plugins.cajon.findOutmostMethodCall
|
||||
import de.platon42.intellij.plugins.cajon.firstArg
|
||||
@@ -21,11 +21,7 @@ class RemoveInstanceOfExpressionQuickFix(description: String, private val replac
|
||||
val factory = JavaPsiFacade.getElementFactory(project)
|
||||
val classObjectAccess = factory.createExpressionFromText("${expectedClass.type.canonicalText}.class", null)
|
||||
|
||||
var operand = assertExpression.operand
|
||||
while (operand is PsiParenthesizedExpression) {
|
||||
operand = operand.expression ?: return
|
||||
}
|
||||
|
||||
val operand = PsiUtil.deparenthesizeExpression(assertExpression.operand) ?: return
|
||||
assertExpression.replace(operand)
|
||||
|
||||
val oldExpectedExpression = element.findOutmostMethodCall() ?: return
|
||||
|
||||
+2
-5
@@ -3,8 +3,8 @@ package de.platon42.intellij.plugins.cajon.quickfixes
|
||||
import com.intellij.codeInspection.ProblemDescriptor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiMethodCallExpression
|
||||
import com.intellij.psi.PsiParenthesizedExpression
|
||||
import com.intellij.psi.PsiUnaryExpression
|
||||
import com.intellij.psi.util.PsiUtil
|
||||
import de.platon42.intellij.plugins.cajon.createExpectedMethodCall
|
||||
import de.platon42.intellij.plugins.cajon.findOutmostMethodCall
|
||||
import de.platon42.intellij.plugins.cajon.firstArg
|
||||
@@ -16,10 +16,7 @@ class RemoveUnaryExpressionQuickFix(description: String, private val replacement
|
||||
val element = descriptor.startElement
|
||||
val methodCallExpression = element as? PsiMethodCallExpression ?: return
|
||||
val assertExpression = methodCallExpression.firstArg as? PsiUnaryExpression ?: return
|
||||
var operand = assertExpression.operand ?: return
|
||||
while (operand is PsiParenthesizedExpression) {
|
||||
operand = operand.expression ?: return
|
||||
}
|
||||
val operand = PsiUtil.skipParenthesizedExprDown(assertExpression.operand) ?: return
|
||||
assertExpression.replace(operand)
|
||||
|
||||
val oldExpectedExpression = element.findOutmostMethodCall() ?: return
|
||||
|
||||
+1
-9
@@ -10,19 +10,11 @@ import com.intellij.psi.util.PsiTypesUtil
|
||||
import com.intellij.util.ArrayUtil
|
||||
import com.intellij.util.ProcessingContext
|
||||
import com.siyeh.ig.callMatcher.CallMatcher
|
||||
import de.platon42.intellij.plugins.cajon.AssertJClassNames
|
||||
import de.platon42.intellij.plugins.cajon.CORE_ASSERT_THAT_MATCHER
|
||||
import de.platon42.intellij.plugins.cajon.firstArg
|
||||
import de.platon42.intellij.plugins.cajon.*
|
||||
|
||||
class ExtractorReferenceContributor : PsiReferenceContributor() {
|
||||
|
||||
companion object {
|
||||
|
||||
private val EXTRACTING_FROM_OBJECT = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_OBJECT_ASSERT_CLASSNAME, "extracting")
|
||||
private val EXTRACTING_FROM_ITERABLE = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_ITERABLE_ASSERT_CLASSNAME, "extracting")
|
||||
private val FLAT_EXTRACTING_FROM_ITERABLE = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_ITERABLE_ASSERT_CLASSNAME, "flatExtracting")
|
||||
private val EXTRACTING_RESULT_OF_FROM_ITERABLE = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_ITERABLE_ASSERT_CLASSNAME, "extractingResultOf")
|
||||
|
||||
private val BY_NAME = CallMatcher.staticCall(AssertJClassNames.EXTRACTORS_CLASSNAME, "byName")
|
||||
private val RESULT_OF = CallMatcher.staticCall(AssertJClassNames.EXTRACTORS_CLASSNAME, "resultOf")
|
||||
.parameterTypes(CommonClassNames.JAVA_LANG_STRING)!!
|
||||
|
||||
@@ -43,6 +43,9 @@
|
||||
<localInspection groupPath="Java" shortName="AssertThatStringExpression" enabledByDefault="true" level="WARNING"
|
||||
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatStringExpressionInspection"/>
|
||||
|
||||
<localInspection groupPath="Java" shortName="JoinAssertThatStatements" enabledByDefault="true" level="WARNING"
|
||||
implementationClass="de.platon42.intellij.plugins.cajon.inspections.JoinAssertThatStatementsInspection"/>
|
||||
|
||||
<localInspection groupPath="Java" shortName="AssertThatJava8Optional" enabledByDefault="true" level="WARNING"
|
||||
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatJava8OptionalInspection"/>
|
||||
<localInspection groupPath="Java" shortName="AssertThatGuavaOptional" enabledByDefault="true" level="WARNING"
|
||||
|
||||
Reference in New Issue
Block a user