New ImplicitAssertion inspection for implicit isNotNull(), isNotEmpty() and isPresent() assertions that will be covered by chained assertions.
This commit is contained in:
@@ -15,6 +15,8 @@ class AssertJClassNames {
|
||||
@NonNls
|
||||
const val EXTENSION_POINTS_INTERFACE = "org.assertj.core.api.ExtensionPoints"
|
||||
@NonNls
|
||||
const val ENUMERABLE_ASSERT_INTERFACE = "org.assertj.core.api.EnumerableAssert"
|
||||
@NonNls
|
||||
const val OBJECT_ENUMERABLE_ASSERT_INTERFACE = "org.assertj.core.api.ObjectEnumerableAssert"
|
||||
|
||||
@NonNls
|
||||
@@ -36,7 +38,7 @@ class AssertJClassNames {
|
||||
@NonNls
|
||||
const val ABSTRACT_ITERABLE_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractIterableAssert"
|
||||
@NonNls
|
||||
const val ABSTRACT_ENUMERABLE_ASSERT_CLASSNAME = "org.assertj.core.api.EnumerableAssert"
|
||||
const val ABSTRACT_OPTIONAL_ASSERT_CLASSNAME = "org.assertj.core.api.AbstractOptionalAssert"
|
||||
@NonNls
|
||||
const val EXTRACTORS_CLASSNAME = "org.assertj.core.extractor.Extractors"
|
||||
|
||||
@@ -44,5 +46,7 @@ class AssertJClassNames {
|
||||
const val GUAVA_OPTIONAL_CLASSNAME = "com.google.common.base.Optional"
|
||||
@NonNls
|
||||
const val GUAVA_ASSERTIONS_CLASSNAME = "org.assertj.guava.api.Assertions"
|
||||
@NonNls
|
||||
const val GUAVA_OPTIONAL_ASSERTIONS_CLASSNAME = "org.assertj.guava.api.OptionalAssert"
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,10 @@ class MethodNames {
|
||||
const val IS_SAME_AS = "isSameAs"
|
||||
@NonNls
|
||||
const val IS_NOT_SAME_AS = "isNotSameAs"
|
||||
|
||||
@NonNls
|
||||
const val HAS_TO_STRING = "hasToString"
|
||||
|
||||
@NonNls
|
||||
const val IS_GREATER_THAN = "isGreaterThan"
|
||||
@NonNls
|
||||
@@ -61,6 +65,8 @@ class MethodNames {
|
||||
@NonNls
|
||||
const val IS_NOT_INSTANCE_OF = "isNotInstanceOf"
|
||||
|
||||
@NonNls
|
||||
const val IS_NULL_OR_EMPTY = "isNullOrEmpty" // terminal, returns void
|
||||
@NonNls
|
||||
const val IS_EMPTY = "isEmpty" // terminal, returns void
|
||||
@NonNls
|
||||
|
||||
+4
-2
@@ -12,10 +12,10 @@ import com.siyeh.ig.callMatcher.CallMatcher
|
||||
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_ASSERT_CLASSNAME
|
||||
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_BOOLEAN_ASSERT_CLASSNAME
|
||||
import de.platon42.intellij.plugins.cajon.AssertJClassNames.Companion.ABSTRACT_COMPARABLE_ASSERT_CLASSNAME
|
||||
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.ENUMERABLE_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
|
||||
@@ -98,7 +98,9 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
|
||||
val IS_NOT_NULL = CallMatcher.instanceCall(ASSERT_INTERFACE, MethodNames.IS_NOT_NULL)
|
||||
.parameterCount(0)!!
|
||||
|
||||
val HAS_SIZE = CallMatcher.instanceCall(ABSTRACT_ENUMERABLE_ASSERT_CLASSNAME, MethodNames.HAS_SIZE)
|
||||
val IS_NOT_EMPTY = CallMatcher.instanceCall(ENUMERABLE_ASSERT_INTERFACE, MethodNames.IS_NOT_EMPTY)
|
||||
.parameterCount(0)!!
|
||||
val HAS_SIZE = CallMatcher.instanceCall(ENUMERABLE_ASSERT_INTERFACE, MethodNames.HAS_SIZE)
|
||||
.parameterTypes("int")!!
|
||||
|
||||
val IS_EQUAL_TO_INT = CallMatcher.instanceCall(ABSTRACT_ASSERT_CLASSNAME, MethodNames.IS_EQUAL_TO)
|
||||
|
||||
+5
@@ -34,6 +34,11 @@ class AssertThatCollectionOrMapExpressionInspection : AbstractAssertJInspection(
|
||||
Mapping(
|
||||
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, "containsValue").parameterCount(1),
|
||||
MethodNames.CONTAINS_VALUE, MethodNames.DOES_NOT_CONTAIN_VALUE
|
||||
),
|
||||
// TODO not quite a collection or map expression, maybe move this out to new inspection together with equals and hashcode.
|
||||
Mapping(
|
||||
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_OBJECT, "toString").parameterCount(0),
|
||||
MethodNames.HAS_TO_STRING, null
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
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.PsiMethodCallExpression
|
||||
import com.siyeh.ig.callMatcher.CallMatcher
|
||||
import de.platon42.intellij.plugins.cajon.*
|
||||
import de.platon42.intellij.plugins.cajon.quickfixes.DeleteMethodCallQuickFix
|
||||
|
||||
class ImplicitAssertionInspection : AbstractAssertJInspection() {
|
||||
|
||||
companion object {
|
||||
private const val DISPLAY_NAME = "Asserting implicitly covered conditions"
|
||||
private const val DELETE_IMPLICIT_DESCRIPTION_TEMPLATE = "Delete implicit %s() covered by %s()"
|
||||
private const val SURPLUS_ASSERTION_MESSAGE = "Implicit %s() assertion is covered by %s()"
|
||||
|
||||
private val IS_PRESENT = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_OPTIONAL_ASSERT_CLASSNAME, MethodNames.IS_PRESENT, MethodNames.IS_NOT_EMPTY)
|
||||
.parameterCount(0)!!
|
||||
private val IS_NOT_PRESENT = CallMatcher.instanceCall(AssertJClassNames.ABSTRACT_OPTIONAL_ASSERT_CLASSNAME, MethodNames.IS_NOT_PRESENT, MethodNames.IS_EMPTY)
|
||||
.parameterCount(0)!!
|
||||
private val OPTIONAL_CONTAINS =
|
||||
CallMatcher.instanceCall(
|
||||
AssertJClassNames.ABSTRACT_OPTIONAL_ASSERT_CLASSNAME,
|
||||
MethodNames.CONTAINS, MethodNames.CONTAINS_SAME,
|
||||
"hasValue", "hasValueSatisfying", "containsInstanceOf"
|
||||
).parameterCount(1)!!
|
||||
|
||||
private val OBJECT_ENUMERABLE_ANY_CONTENT_ASSERTIONS = CallMatcher.instanceCall(
|
||||
AssertJClassNames.OBJECT_ENUMERABLE_ASSERT_INTERFACE,
|
||||
MethodNames.CONTAINS, "containsOnly", "containsOnlyNulls", "containsOnlyOnce",
|
||||
"containsExactly", "containsExactlyInAnyOrder", "containsExactlyInAnyOrderElementsOf",
|
||||
"containsAll", "containsAnyOf",
|
||||
"containsAnyElementsOf", "containsExactlyElementsOf", "containsOnlyElementsOf",
|
||||
"isSubsetOf", "containsSequence", "containsSubsequence",
|
||||
"doesNotContainSequence", "doesNotContainSubsequence", "doesNotContain",
|
||||
"doesNotContainAnyElementsOf", "doesNotHaveDuplicates",
|
||||
"startsWith", "endsWith", "containsNull", "doesNotContainNull",
|
||||
"are", "areNot", "have", "doNotHave", "areAtLeastOne", "areAtLeast", "areAtMost", "areExactly",
|
||||
"haveAtLeastOne", "haveAtLeast", "haveAtMost", "haveExactly",
|
||||
"doesNotHave", "doesNotHaveSameClassAs",
|
||||
"hasAtLeastOneElementOfType", "hasOnlyElementsOfType", "hasOnlyElementsOfTypes",
|
||||
"doesNotHaveAnyElementsOfTypes",
|
||||
"has", "doesNotHave",
|
||||
"hasOnlyOneElementSatisfying", "hasSameElementsAs",
|
||||
"allMatch", "allSatisfy", "anyMatch", "anySatisfy", "noneMatch", "noneSatisfy"
|
||||
)!!
|
||||
|
||||
private val OBJECT_ENUMERABLE_AT_LEAST_ONE_CONTENT_ASSERTIONS = CallMatcher.instanceCall(
|
||||
AssertJClassNames.OBJECT_ENUMERABLE_ASSERT_INTERFACE,
|
||||
"containsOnlyNulls",
|
||||
"startsWith", "endsWith", "containsNull",
|
||||
"areAtLeastOne",
|
||||
"haveAtLeastOne",
|
||||
"hasAtLeastOneElementOfType",
|
||||
"anyMatch", "anySatisfy"
|
||||
)!!
|
||||
|
||||
private val ENUMERABLE_NON_NULL_ASSERTIONS = CallMatcher.instanceCall(
|
||||
AssertJClassNames.ENUMERABLE_ASSERT_INTERFACE,
|
||||
MethodNames.IS_EMPTY, MethodNames.IS_NOT_EMPTY,
|
||||
MethodNames.HAS_SIZE, MethodNames.HAS_SIZE_GREATER_THAN, MethodNames.HAS_SIZE_GREATER_THAN_OR_EQUAL_TO,
|
||||
MethodNames.HAS_SIZE_LESS_THAN, MethodNames.HAS_SIZE_LESS_THAN_OR_EQUAL_TO,
|
||||
"hasSizeBetween", MethodNames.HAS_SAME_SIZE_AS
|
||||
)!!
|
||||
|
||||
private val ENUMERABLE_AT_LEAST_ONE_CONTENT_ASSERTIONS = CallMatcher.instanceCall(
|
||||
AssertJClassNames.ENUMERABLE_ASSERT_INTERFACE,
|
||||
MethodNames.HAS_SIZE, MethodNames.HAS_SIZE_GREATER_THAN,
|
||||
MethodNames.HAS_SAME_SIZE_AS
|
||||
)!!
|
||||
|
||||
private val NON_NULL_CORE_ASSERTIONS = CallMatcher.instanceCall(
|
||||
AssertJClassNames.ASSERT_INTERFACE,
|
||||
"isInstanceOf", "isInstanceOfSatisfying", "isInstanceOfAny", "isExactlyInstanceOf", "isOfAnyClassIn",
|
||||
"isNotInstanceOf", "isNotInstanceOfAny", "isNotExactlyInstanceOf", "isNotOfAnyClassIn",
|
||||
"hasSameClassAs", "doesNotHaveSameClassAs",
|
||||
"hasToString", "hasSameHashCodeAs"
|
||||
)!!
|
||||
|
||||
private val GUAVA_IS_PRESENT = CallMatcher.instanceCall(AssertJClassNames.GUAVA_OPTIONAL_ASSERTIONS_CLASSNAME, MethodNames.IS_PRESENT)
|
||||
.parameterCount(0)!!
|
||||
private val GUAVA_IS_ABSENT = CallMatcher.instanceCall(AssertJClassNames.GUAVA_OPTIONAL_ASSERTIONS_CLASSNAME, MethodNames.IS_ABSENT)
|
||||
.parameterCount(0)!!
|
||||
private val GUAVA_OPTIONAL_CONTAINS = CallMatcher.instanceCall(
|
||||
AssertJClassNames.GUAVA_OPTIONAL_ASSERTIONS_CLASSNAME,
|
||||
MethodNames.CONTAINS, "extractingValue", "extractingCharSequence"
|
||||
)!!
|
||||
|
||||
private val MAPPINGS = listOf(
|
||||
IS_NOT_NULL to CallMatcher.anyOf(
|
||||
NON_NULL_CORE_ASSERTIONS,
|
||||
ENUMERABLE_NON_NULL_ASSERTIONS,
|
||||
OBJECT_ENUMERABLE_ANY_CONTENT_ASSERTIONS,
|
||||
IS_PRESENT, IS_NOT_PRESENT, OPTIONAL_CONTAINS,
|
||||
GUAVA_IS_PRESENT, GUAVA_IS_ABSENT, GUAVA_OPTIONAL_CONTAINS
|
||||
)!!,
|
||||
|
||||
IS_NOT_EMPTY to CallMatcher.anyOf(
|
||||
ENUMERABLE_AT_LEAST_ONE_CONTENT_ASSERTIONS,
|
||||
OBJECT_ENUMERABLE_AT_LEAST_ONE_CONTENT_ASSERTIONS
|
||||
)!!,
|
||||
|
||||
IS_PRESENT to OPTIONAL_CONTAINS,
|
||||
GUAVA_IS_PRESENT to GUAVA_OPTIONAL_CONTAINS
|
||||
)
|
||||
}
|
||||
|
||||
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 (!expression.hasAssertThat()) {
|
||||
return
|
||||
}
|
||||
val mapping = MAPPINGS.firstOrNull { it.first.test(expression) } ?: return
|
||||
val followupExpression = expression.findFluentCallTo(mapping.second) ?: return
|
||||
val redundantName = getOriginalMethodName(expression) ?: return
|
||||
val followupName = getOriginalMethodName(followupExpression) ?: return
|
||||
val description = DELETE_IMPLICIT_DESCRIPTION_TEMPLATE.format(redundantName, followupName)
|
||||
val quickFix = DeleteMethodCallQuickFix(description)
|
||||
|
||||
val textRange = TextRange(expression.qualifierExpression.textLength, expression.textLength)
|
||||
holder.registerProblem(expression, textRange, SURPLUS_ASSERTION_MESSAGE.format(redundantName, followupName), quickFix)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package de.platon42.intellij.plugins.cajon.quickfixes
|
||||
|
||||
import com.intellij.codeInspection.ProblemDescriptor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiMethodCallExpression
|
||||
import de.platon42.intellij.plugins.cajon.qualifierExpression
|
||||
|
||||
class DeleteMethodCallQuickFix(description: String) : AbstractCommonQuickFix(description) {
|
||||
|
||||
companion object {
|
||||
private const val DELETE_METHOD_DESCRIPTION = "Delete unnecessary method calls"
|
||||
}
|
||||
|
||||
override fun getFamilyName(): String {
|
||||
return DELETE_METHOD_DESCRIPTION
|
||||
}
|
||||
|
||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||
val methodCallExpression = descriptor.startElement as? PsiMethodCallExpression ?: return
|
||||
|
||||
methodCallExpression.replace(methodCallExpression.qualifierExpression)
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,9 @@
|
||||
<localInspection groupPath="Java" shortName="AssertThatGuavaOptional" enabledByDefault="true" level="WARNING"
|
||||
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatGuavaOptionalInspection"/>
|
||||
|
||||
<localInspection groupPath="Java" shortName="ImplicitAssertion" enabledByDefault="true" level="WARNING"
|
||||
implementationClass="de.platon42.intellij.plugins.cajon.inspections.ImplicitAssertionInspection"/>
|
||||
|
||||
<localInspection groupPath="Java" shortName="JUnitAssertToAssertJ" enabledByDefault="true" level="WARNING"
|
||||
implementationClass="de.platon42.intellij.plugins.cajon.inspections.JUnitAssertToAssertJInspection"/>
|
||||
</extensions>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<body>
|
||||
Finds assertion method calls that are already implicitly covered by prior assertions.
|
||||
<!-- tooltip end -->
|
||||
This works for isNotNull(), isNotEmpty() and isPresent(). It does not cover all edge cases, such as .isNotNull().isNullOrEmpty()
|
||||
or comparing against non-null literals and is conservative to not eliminate legitimate assertions.
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,9 +1,10 @@
|
||||
<html>
|
||||
<body>
|
||||
Tries to convert most of the JUnit 4 assertions to AssertJ format.
|
||||
Tries to convert most of the JUnit 4 assertions and assumptions to AssertJ format.
|
||||
<!-- tooltip end -->
|
||||
<br>Works for assertTrue(), assertFalse(), assertNull(), assertNotNull(), assertEquals(), assertNotEquals(), assertSame() assertNotSame(), assertArrayEquals().
|
||||
Copes with variants with message and without, handles special versions for double and float types (including arrays).
|
||||
Also converts assumeTrue(), assumeFalse(), assumeNotNull(), assumeNoException() to assumeThat().
|
||||
<p>
|
||||
Does not support Hamcrest-Matchers. If you need that kind of conversion, you might want to check out the Assertions2AssertJ plugin by Ric Emery.
|
||||
</p>
|
||||
|
||||
Reference in New Issue
Block a user