New JoinVarArgsContains inspection that will detect multiple .contains(), .doesNotContain(), and .containsOnlyOnce() calls within the same statement that could be joined together using variadic arguments.
This commit is contained in:
@@ -36,14 +36,18 @@ val MORE_EXTENSION_POINTS = CallMatcher.instanceCall(
|
||||
"hasOnlyOneElementSatisfying", "anyMatch", "noneMatch", "anySatisfy", "noneSatisfy"
|
||||
)!!
|
||||
|
||||
val NOT_ACTUAL_ASSERTIONS = CallMatcher.anyOf(
|
||||
ALL_ASSERT_THAT_MATCHERS,
|
||||
val COMPLEX_CALLS_THAT_MAKES_STUFF_TRICKY = CallMatcher.anyOf(
|
||||
DESCRIBED_AS,
|
||||
WITH_REPRESENTATION_AND_SUCH,
|
||||
USING_COMPARATOR,
|
||||
IN_HEXADECIMAL_OR_BINARY
|
||||
)!!
|
||||
|
||||
val NOT_ACTUAL_ASSERTIONS = CallMatcher.anyOf(
|
||||
ALL_ASSERT_THAT_MATCHERS,
|
||||
COMPLEX_CALLS_THAT_MAKES_STUFF_TRICKY
|
||||
)!!
|
||||
|
||||
val KNOWN_METHODS_WITH_SIDE_EFFECTS = CallMatcher.anyOf(
|
||||
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_ITERATOR, "next")
|
||||
)!!
|
||||
@@ -86,6 +86,8 @@ class MethodNames {
|
||||
@NonNls
|
||||
const val CONTAINS = "contains"
|
||||
@NonNls
|
||||
const val CONTAINS_ONLY_ONCE = "containsOnlyOnce"
|
||||
@NonNls
|
||||
const val DOES_NOT_CONTAIN = "doesNotContain"
|
||||
@NonNls
|
||||
const val CONTAINS_EXACTLY = "containsExactly"
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ class ImplicitAssertionInspection : AbstractAssertJInspection() {
|
||||
|
||||
private val OBJECT_ENUMERABLE_ANY_CONTENT_ASSERTIONS = CallMatcher.instanceCall(
|
||||
AssertJClassNames.OBJECT_ENUMERABLE_ASSERT_INTERFACE,
|
||||
MethodNames.CONTAINS, "containsOnly", "containsOnlyNulls", "containsOnlyOnce",
|
||||
MethodNames.CONTAINS, "containsOnly", "containsOnlyNulls", MethodNames.CONTAINS_ONLY_ONCE,
|
||||
"containsExactly", "containsExactlyInAnyOrder", "containsExactlyInAnyOrderElementsOf",
|
||||
"containsAll", "containsAnyOf",
|
||||
"containsAnyElementsOf", "containsExactlyElementsOf", "containsOnlyElementsOf",
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package de.platon42.intellij.plugins.cajon.inspections
|
||||
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.JavaElementVisitor
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.psi.PsiExpressionStatement
|
||||
import com.intellij.psi.PsiMethodCallExpression
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.siyeh.ig.callMatcher.CallMatcher
|
||||
import de.platon42.intellij.plugins.cajon.*
|
||||
import de.platon42.intellij.plugins.cajon.quickfixes.JoinVarArgsContainsQuickFix
|
||||
|
||||
class JoinVarArgsContainsInspection : AbstractAssertJInspection() {
|
||||
|
||||
companion object {
|
||||
private const val DISPLAY_NAME = "Join variadic arguments of contains()/containsOnlyOnce()/doesNotContain()"
|
||||
private const val JOIN_VARARGS_MESSAGE = "Calls to same methods may be joined to variadic version"
|
||||
|
||||
private val MATCHERS = listOf(MethodNames.CONTAINS, MethodNames.CONTAINS_ONLY_ONCE, MethodNames.DOES_NOT_CONTAIN)
|
||||
.map { CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_ITERABLE_ASSERT_CLASSNAME, it) }
|
||||
}
|
||||
|
||||
override fun getDisplayName() = DISPLAY_NAME
|
||||
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
||||
return object : JavaElementVisitor() {
|
||||
override fun visitExpressionStatement(statement: PsiExpressionStatement) {
|
||||
super.visitStatement(statement)
|
||||
if (!statement.hasAssertThat()) return
|
||||
val assertThatCall = PsiTreeUtil.findChildrenOfType(statement, PsiMethodCallExpression::class.java).find { ALL_ASSERT_THAT_MATCHERS.test(it) } ?: return
|
||||
|
||||
val allCalls = assertThatCall.collectMethodCallsUpToStatement().toList()
|
||||
|
||||
if (allCalls.find(COMPLEX_CALLS_THAT_MAKES_STUFF_TRICKY::test) != null) return
|
||||
|
||||
val onlyAssertionCalls = allCalls
|
||||
.filterNot { NOT_ACTUAL_ASSERTIONS.test(it) }
|
||||
.toList()
|
||||
|
||||
for (methodMatcher in MATCHERS) {
|
||||
if (onlyAssertionCalls.count(methodMatcher::test) > 1) {
|
||||
val outmostMethodCall = statement.findOutmostMethodCall() ?: return
|
||||
val quickFix = JoinVarArgsContainsQuickFix(MATCHERS)
|
||||
val textRange = TextRange(outmostMethodCall.qualifierExpression.textLength, outmostMethodCall.textLength)
|
||||
holder.registerProblem(outmostMethodCall, textRange, JOIN_VARARGS_MESSAGE, quickFix)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
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.siyeh.ig.callMatcher.CallMatcher
|
||||
import de.platon42.intellij.plugins.cajon.*
|
||||
|
||||
class JoinVarArgsContainsQuickFix(private val matchers: Iterable<CallMatcher>) : AbstractCommonQuickFix(JOIN_VARARGS_DESCRIPTION) {
|
||||
|
||||
companion object {
|
||||
private const val JOIN_VARARGS_DESCRIPTION = "Join multiple arguments to variadic argument method calls"
|
||||
}
|
||||
|
||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||
var outmostCallExpression = descriptor.startElement as? PsiMethodCallExpression ?: return
|
||||
|
||||
for (matcher in matchers) {
|
||||
val assertThatMethodCall = outmostCallExpression.findStaticMethodCall() ?: return
|
||||
val methodsToFix = assertThatMethodCall.gatherAssertionCalls()
|
||||
val matchedCalls = methodsToFix.filter(matcher::test)
|
||||
if (matchedCalls.size > 1) {
|
||||
val mainCall = matchedCalls.first()
|
||||
val args = mutableListOf(*mainCall.argumentList.expressions)
|
||||
for (secondaryCall in matchedCalls.asSequence().drop(1)) {
|
||||
args.addAll(secondaryCall.argumentList.expressions)
|
||||
}
|
||||
val newMainCall = createExpectedMethodCall(mainCall, mainCall.methodExpression.qualifiedName, *args.toTypedArray())
|
||||
newMainCall.replaceQualifierFromMethodCall(mainCall)
|
||||
mainCall.replace(newMainCall)
|
||||
for (secondaryCall in matchedCalls.asSequence().drop(1)) {
|
||||
val newQualifier = secondaryCall.qualifierExpression
|
||||
outmostCallExpression = secondaryCall.replace(newQualifier).findOutmostMethodCall() ?: return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,8 @@
|
||||
|
||||
<localInspection groupPath="Java" shortName="JoinAssertThatStatements" enabledByDefault="true" level="WARNING"
|
||||
implementationClass="de.platon42.intellij.plugins.cajon.inspections.JoinAssertThatStatementsInspection"/>
|
||||
<localInspection groupPath="Java" shortName="JoinVarArgsContains" enabledByDefault="true" level="WARNING"
|
||||
implementationClass="de.platon42.intellij.plugins.cajon.inspections.JoinVarArgsContainsInspection"/>
|
||||
<localInspection groupPath="Java" shortName="AssumeThatInsteadOfReturn" enabledByDefault="true" level="WARNING"
|
||||
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssumeThatInsteadOfReturnInspection"/>
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<html>
|
||||
<body>
|
||||
Finds assertions where multiple .contains(), .containsOnlyOnce() or .doesNotContain() are
|
||||
used in a single statement that could be joined together.
|
||||
<!-- tooltip end -->
|
||||
Only works when variadic arguments are possible and will not be performed on more complex
|
||||
statements with .extracting() or .as() to avoid changing semantics.
|
||||
<br>
|
||||
Note that the quickfix does not handle comments very well and might remove them during the operation.
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user