Optimized even more code. Fixed BinaryExpression for Boolean.TRUE/FALSE. Added new AssertThatJava8Optional inspection. Added changelog to README.

This commit is contained in:
Chris Hodges 2019-04-11 16:26:11 +02:00
parent c1d8ade7b1
commit d49b7bf17b
18 changed files with 528 additions and 154 deletions

117
README.md
View File

@ -32,6 +32,15 @@ Then AssertJ would tell you the contents of the collection on failure.
The plugin also supports the conversion of the most common JUnit 4 assertions to AssertJ.
## Usage
The plugin will report inspections in your opened editor file as warnings.
You can then quick-fix these with your quick-fix hotkey (usually Alt-Return or Opt-Return).
Or, you can use the "Run Inspection by Name..." action to run one inspection on a bigger scope (e.g. the whole project).
You can toggle the various inspections in the Settings/Editor/Inspections in the AssertJ group.
## Implemented inspections
- AssertThatObjectIsNullOrNotNull
@ -108,7 +117,36 @@ The plugin also supports the conversion of the most common JUnit 4 assertions to
to: assertThat(objActual).isNotNull();
```
and many, many more combinations (more than 150).
- AssertThatJava8Optional
```
from: assertThat(opt.isPresent()).isEqualTo(true);
from: assertThat(opt.isPresent()).isNotEqualTo(false);
from: assertThat(opt.isPresent()).isTrue();
to: assertThat(opt).isPresent();
from: assertThat(opt.isPresent()).isEqualTo(false);
from: assertThat(opt.isPresent()).isNotEqualTo(true);
from: assertThat(opt.isPresent()).isFalse();
to: assertThat(opt).isNotPresent();
from: assertThat(opt.get()).isEqualTo("foo");
to: assertThat(opt).contains("foo");
from: assertThat(opt.get()).isSameAs("foo");
to: assertThat(opt).containsSame("foo");
from: assertThat(opt).isEqualTo(Optional.of("foo"));
from: assertThat(opt).isEqualTo(Optional.ofNullable("foo"));
to: assertThat(opt).contains("foo");
from: assertThat(opt).isEqualTo(Optional.empty());
to: assertThat(opt).isNotPresent();
from: assertThat(opt).isNotEqualTo(Optional.empty());
to: assertThat(opt).isPresent();
```
- JUnitAssertToAssertJ
```
assertTrue(condition);
@ -139,49 +177,43 @@ The plugin also supports the conversion of the most common JUnit 4 assertions to
## Development notice
Cajon is written in Kotlin 1.3.
Cajon is probably the only plugin that uses JUnit 5 Jupiter for unit testing so far (or at least the only one that I'm aware of ;) ).
The IntelliJ framework actually uses the JUnit 3 TestCase for plugin testing and I took me quite a while to make it work with JUnit 5.
Feel free to use the code (in package de.platon42.intellij.jupiter) for your projects (with attribution).
## TODO
- AssertThatJava8OptionalContains
- AssertThatNegatedBooleanExpression
- AssertThatGuavaOptional
```
from: assertThat(Optional.of("foo").get()).isEqualTo("foo");
to: assertThat(Optional.of("foo")).contains("foo");
```
- AssertThatJava8OptionalIsPresentOrAbsent
```
from: assertThat(Optional.of("foo").isPresent()).isEqualTo(true);
from: assertThat(!Optional.of("foo").isPresent()).isEqualTo(false);
from: assertThat(Optional.of("foo").isPresent()).isTrue();
from: assertThat(!Optional.of("foo").isPresent()).isFalse();
to: assertThat(Optional.of("foo")).isPresent();
from: assertThat(opt.isPresent()).isEqualTo(true);
from: assertThat(opt.isPresent()).isNotEqualTo(false);
from: assertThat(opt.isPresent()).isTrue();
to: assertThat(opt).isPresent();
from: assertThat(Optional.of("foo").isPresent()).isEqualTo(false);
from: assertThat(!Optional.of("foo").isPresent()).isEqualTo(true);
from: assertThat(Optional.of("foo").isPresent()).isFalse();
from: assertThat(!Optional.of("foo").isPresent()).isTrue();
to: assertThat(Optional.of("foo")).isNotPresent();
```
- AssertThatGuavaOptionalContains
```
from: assertThat(Optional.of("foo").get()).isEqualTo("foo");
to: assertThat(Optional.of("foo")).contains("foo");
```
- AssertThatGuavaOptionalIsPresentOrAbsent
```
from: assertThat(Optional.of("foo").isPresent()).isEqualTo(true);
from: assertThat(!Optional.of("foo").isPresent()).isEqualTo(false);
from: assertThat(Optional.of("foo").isPresent()).isTrue();
from: assertThat(!Optional.of("foo").isPresent()).isFalse();
to: assertThat(Optional.of("foo")).isPresent();
from: assertThat(opt.isPresent()).isEqualTo(false);
from: assertThat(opt.isPresent()).isNotEqualTo(true);
from: assertThat(opt.isPresent()).isFalse();
to: assertThat(opt).isAbsent();
from: assertThat(opt.get()).isEqualTo("foo");
to: assertThat(opt).contains("foo");
from: assertThat(opt.get()).isSameAs("foo");
to: assertThat(opt).containsSame("foo");
from: assertThat(Optional.of("foo").isPresent()).isEqualTo(false);
from: assertThat(!Optional.of("foo").isPresent()).isEqualTo(true);
from: assertThat(Optional.of("foo").isPresent()).isFalse();
from: assertThat(!Optional.of("foo").isPresent()).isTrue();
to: assertThat(Optional.of("foo")).isAbsent();
from: assertThat(opt).isEqualTo(Optional.of("foo"));
from: assertThat(opt).isEqualTo(Optional.fromNullable("foo"));
to: assertThat(opt).contains("foo");
from: assertThat(opt).isEqualTo(Optional.absent());
to: assertThat(opt).isAbsent();
from: assertThat(opt).isNotEqualTo(Optional.absent());
to: assertThat(opt).isPresent();
```
- Referencing string properties inside extracting()
- Extraction with property names to lambda with Java 8
```
@ -189,3 +221,18 @@ Feel free to use the code (in package de.platon42.intellij.jupiter) for your pro
to: assertThat(object).extracting(type::getPropOne, it -> it.propNoGetter, it -> it.getPropTwo().getInnerProp())...
```
- Kotlin support
## Changelog
#### V0.3 (07-Apr-19)
- New inspection AssertThatBinaryExpressionIsTrueOrFalse that will find and fix common binary expressions and equals() statements (more than 150 combinations) inside assertThat().
- Merged AssertThatObjectIsNull and AssertThatObjectIsNotNull to AssertThatObjectIsNullOrNotNull.
- Support for hasSizeLessThan(), hasSizeLessThanOrEqualTo(), hasSizeGreaterThanOrEqualTo(), and hasSizeGreaterThan() for AssertThatSizeInspection (with AssertJ >=13.2.0).
- Really fixed highlighting for JUnit conversion. Sorry.
#### V0.2 (01-Apr-19)
- Fixed descriptions and quick fix texts.
- Fixed highlighting of found problems and also 'Run inspection by Name' returning nothing.
#### V0.1 (31-Mar-19)
- Initial release.

View File

@ -43,7 +43,7 @@ patchPluginXml {
changeNotes """
<h4>V0.4 (xx-Apr-19)</h4>
<ul>
<li>Yo.
<li>New inspection AssertThatJava8Optional that operates on Java 8 Optional objects and tries to use contains(), isPresent() and isNotPresent() instead.
</ul>
<h4>V0.3 (07-Apr-19)</h4>
<ul>

View File

@ -4,10 +4,13 @@ import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.*
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.tree.IElementType
import com.intellij.psi.util.PsiTypesUtil
import com.siyeh.ig.callMatcher.CallMatcher
import de.platon42.intellij.plugins.cajon.getArg
import de.platon42.intellij.plugins.cajon.qualifierExpression
import de.platon42.intellij.plugins.cajon.quickfixes.RemoveActualOutmostMethodCallQuickFix
import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceExpectedOutmostMethodCallQuickFix
import de.platon42.intellij.plugins.cajon.quickfixes.ReplaceSimpleMethodCallQuickFix
import org.jetbrains.annotations.NonNls
@ -15,6 +18,7 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
companion object {
const val SIMPLIFY_MESSAGE_TEMPLATE = "%s can be simplified to %s"
const val MORE_CONCISE_MESSAGE_TEMPLATE = "%s would be more concise than %s"
const val REPLACE_DESCRIPTION_TEMPLATE = "Replace %s with %s"
@ -43,6 +47,10 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
@NonNls
const val IS_NOT_EQUAL_TO_METHOD = "isNotEqualTo"
@NonNls
const val IS_SAME_AS_METHOD = "isSameAs"
@NonNls
const val IS_NOT_SAME_AS_METHOD = "isNotSameAs"
@NonNls
const val IS_GREATER_THAN_METHOD = "isGreaterThan"
@NonNls
const val IS_GREATER_THAN_OR_EQUAL_TO_METHOD = "isGreaterThanOrEqualTo"
@ -61,12 +69,50 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
@NonNls
const val HAS_SIZE_METHOD = "hasSize"
val TOKEN_TO_ASSERTJ_FOR_PRIMITIVE_MAP = mapOf<IElementType, String>(
JavaTokenType.EQEQ to IS_EQUAL_TO_METHOD,
JavaTokenType.NE to IS_NOT_EQUAL_TO_METHOD,
JavaTokenType.GT to IS_GREATER_THAN_METHOD,
JavaTokenType.GE to IS_GREATER_THAN_OR_EQUAL_TO_METHOD,
JavaTokenType.LT to IS_LESS_THAN_METHOD,
JavaTokenType.LE to IS_LESS_THAN_OR_EQUAL_TO_METHOD
)
val TOKEN_TO_ASSERTJ_FOR_OBJECT_MAPPINGS = mapOf<IElementType, String>(
JavaTokenType.EQEQ to IS_SAME_AS_METHOD,
JavaTokenType.NE to IS_NOT_SAME_AS_METHOD
)
val SWAP_SIDE_OF_BINARY_OPERATOR = mapOf<IElementType, IElementType>(
JavaTokenType.GT to JavaTokenType.LT,
JavaTokenType.GE to JavaTokenType.LE,
JavaTokenType.LT to JavaTokenType.GT,
JavaTokenType.LE to JavaTokenType.GE
)
val INVERT_BINARY_OPERATOR = mapOf<IElementType, IElementType>(
JavaTokenType.EQEQ to JavaTokenType.NE,
JavaTokenType.NE to JavaTokenType.EQEQ,
JavaTokenType.GT to JavaTokenType.LE,
JavaTokenType.GE to JavaTokenType.LT,
JavaTokenType.LT to JavaTokenType.GE,
JavaTokenType.LE to JavaTokenType.GT
)
val ASSERT_THAT_INT = CallMatcher.staticCall(ASSERTIONS_CLASSNAME, ASSERT_THAT_METHOD)
.parameterTypes("int")!!
val ASSERT_THAT_BOOLEAN = CallMatcher.staticCall(ASSERTIONS_CLASSNAME, ASSERT_THAT_METHOD)
.parameterTypes("boolean")!!
val ASSERT_THAT_ANY = CallMatcher.staticCall(ASSERTIONS_CLASSNAME, ASSERT_THAT_METHOD)
.parameterCount(1)!!
val ASSERT_THAT_JAVA8_OPTIONAL = CallMatcher.staticCall(ASSERTIONS_CLASSNAME, ASSERT_THAT_METHOD)
.parameterTypes(CommonClassNames.JAVA_UTIL_OPTIONAL)!!
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)
@ -76,6 +122,11 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
val IS_NOT_EQUAL_TO_BOOLEAN =
CallMatcher.instanceCall(ABSTRACT_BOOLEAN_ASSERT_CLASSNAME, IS_NOT_EQUAL_TO_METHOD)
.parameterTypes("boolean")!!
val IS_SAME_AS_OBJECT = CallMatcher.instanceCall(ABSTRACT_ASSERT_CLASSNAME, IS_SAME_AS_METHOD)
.parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!!
val IS_NOT_SAME_AS_OBJECT = CallMatcher.instanceCall(ABSTRACT_ASSERT_CLASSNAME, IS_NOT_SAME_AS_METHOD)
.parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!!
val HAS_SIZE = CallMatcher.instanceCall(ABSTRACT_ENUMERABLE_ASSERT_CLASSNAME, HAS_SIZE_METHOD)
.parameterTypes("int")!!
@ -105,6 +156,18 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
.parameterCount(0)!!
val OBJECT_EQUALS = CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_OBJECT, "equals")
.parameterTypes(CommonClassNames.JAVA_LANG_OBJECT)!!
val OPTIONAL_GET = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "get")
.parameterCount(0)!!
val OPTIONAL_IS_PRESENT = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "isPresent")
.parameterCount(0)!!
val OPTIONAL_OF = CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "of")
.parameterCount(1)!!
val OPTIONAL_OF_NULLABLE = CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "ofNullable")
.parameterCount(1)!!
val OPTIONAL_EMPTY = CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "empty")
.parameterCount(0)!!
}
override fun getGroupDisplayName(): String {
@ -137,6 +200,33 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
holder.registerProblem(expression, message, quickFix)
}
protected fun registerRemoveActualOutmostMethod(
holder: ProblemsHolder,
expression: PsiMethodCallExpression,
expectedCallExpression: PsiMethodCallExpression,
replacementMethod: String,
noExpectedExpression: Boolean = false
) {
val originalMethod = getOriginalMethodName(expectedCallExpression) ?: return
val description = REPLACE_DESCRIPTION_TEMPLATE.format(originalMethod, replacementMethod)
val message = MORE_CONCISE_MESSAGE_TEMPLATE.format(replacementMethod, originalMethod)
val quickfix = RemoveActualOutmostMethodCallQuickFix(description, replacementMethod, noExpectedExpression)
holder.registerProblem(expression, message, quickfix)
}
protected fun registerRemoveExpectedOutmostMethod(
holder: ProblemsHolder,
expression: PsiMethodCallExpression,
expectedCallExpression: PsiMethodCallExpression,
replacementMethod: String
) {
val originalMethod = getOriginalMethodName(expectedCallExpression) ?: return
val description = REPLACE_DESCRIPTION_TEMPLATE.format(originalMethod, replacementMethod)
val message = MORE_CONCISE_MESSAGE_TEMPLATE.format(replacementMethod, originalMethod)
val quickfix = ReplaceExpectedOutmostMethodCallQuickFix(description, replacementMethod)
holder.registerProblem(expression, message, quickfix)
}
protected fun calculateConstantParameterValue(expression: PsiMethodCallExpression, argIndex: Int): Any? {
if (argIndex >= expression.argumentList.expressionCount) return null
val valueExpression = expression.getArg(argIndex)
@ -155,6 +245,22 @@ open class AbstractAssertJInspection : AbstractBaseJavaLocalInspectionTool() {
return value
}
protected fun getExpectedBooleanResult(expectedCallExpression: PsiMethodCallExpression): Boolean? {
val isTrue = IS_TRUE.test(expectedCallExpression)
val isFalse = IS_FALSE.test(expectedCallExpression)
if (isTrue || isFalse) {
return isTrue
} else {
val isEqualTo = IS_EQUAL_TO_BOOLEAN.test(expectedCallExpression) || IS_EQUAL_TO_OBJECT.test(expectedCallExpression)
val isNotEqualTo = IS_NOT_EQUAL_TO_BOOLEAN.test(expectedCallExpression) || IS_NOT_EQUAL_TO_OBJECT.test(expectedCallExpression)
if (isEqualTo || isNotEqualTo) {
val constValue = calculateConstantParameterValue(expectedCallExpression, 0) as? Boolean ?: return null
return isNotEqualTo xor constValue
}
}
return null
}
protected fun hasAssertJMethod(element: PsiElement, classAndMethod: String): Boolean {
val classname = "org.assertj.core.api.${classAndMethod.substringBeforeLast(".")}"
val findClass =

View File

@ -2,7 +2,6 @@ package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.*
import com.intellij.psi.tree.IElementType
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.TypeConversionUtil
import de.platon42.intellij.plugins.cajon.firstArg
@ -17,27 +16,6 @@ class AssertThatBinaryExpressionIsTrueOrFalseInspection : AbstractAssertJInspect
private const val SPLIT_EQUALS_EXPRESSION_DESCRIPTION = "Split equals() expression out of assertThat()"
private const val BINARY_MORE_MEANINGFUL_MESSAGE = "Moving binary expression out of assertThat() would be more meaningful"
private const val EQUALS_MORE_MEANINGFUL_MESSAGE = "Moving equals() expression out of assertThat() would be more meaningful"
private val PRIMITIVE_MAPPINGS = listOf(
Mapping(JavaTokenType.EQEQ, "isEqualTo()", "isNotEqualTo()"),
Mapping(JavaTokenType.NE, "isNotEqualTo()", "isEqualTo()"),
Mapping(JavaTokenType.GT, "isGreaterThan()", "isLessThanOrEqualTo()"),
Mapping(JavaTokenType.GE, "isGreaterThanOrEqualTo()", "isLessThan()"),
Mapping(JavaTokenType.LT, "isLessThan()", "isGreaterThanOrEqualTo()"),
Mapping(JavaTokenType.LE, "isLessThanOrEqualTo()", "isGreaterThan()")
)
private val OBJECT_MAPPINGS = listOf(
Mapping(JavaTokenType.EQEQ, "isSameAs()", "isNotSameAs()"),
Mapping(JavaTokenType.NE, "isNotSameAs()", "isSameAs()")
)
private val SWAP_BINARY_OPERATOR = mapOf<IElementType, IElementType>(
JavaTokenType.GT to JavaTokenType.LT,
JavaTokenType.GE to JavaTokenType.LE,
JavaTokenType.LT to JavaTokenType.GT,
JavaTokenType.LE to JavaTokenType.GE
)
}
override fun getDisplayName() = DISPLAY_NAME
@ -52,11 +30,11 @@ class AssertThatBinaryExpressionIsTrueOrFalseInspection : AbstractAssertJInspect
val statement = PsiTreeUtil.getParentOfType(expression, PsiStatement::class.java) ?: return
val expectedCallExpression = PsiTreeUtil.findChildOfType(statement, PsiMethodCallExpression::class.java) ?: return
val isInverted = getExpectedResult(expectedCallExpression) ?: return
val expectedResult = getExpectedBooleanResult(expectedCallExpression) ?: return
val assertThatArgument = expression.firstArg
if (assertThatArgument is PsiMethodCallExpression && OBJECT_EQUALS.test(assertThatArgument)) {
val replacementMethod = if (isInverted) "isNotEqualTo()" else "isEqualTo()"
val replacementMethod = if (expectedResult) "isEqualTo()" else "isNotEqualTo()"
val quickFix = SplitEqualsExpressionMethodCallQuickFix(SPLIT_EQUALS_EXPRESSION_DESCRIPTION, replacementMethod)
holder.registerProblem(expression, EQUALS_MORE_MEANINGFUL_MESSAGE, quickFix)
return
@ -72,13 +50,8 @@ class AssertThatBinaryExpressionIsTrueOrFalseInspection : AbstractAssertJInspect
if (isLeftNull && isRightNull) {
return
} else if (isLeftNull || isRightNull) {
registerSplitBinaryExpressionMethod(
holder,
expression,
if (isInverted) "isNotNull()" else "isNull()",
pickRightOperand = isLeftNull,
noExpectedExpression = true
)
val replacementMethod = if (expectedResult) "isNull()" else "isNotNull()"
registerSplitBinaryExpressionMethod(holder, expression, replacementMethod, pickRightOperand = isLeftNull, noExpectedExpression = true)
return
}
@ -87,35 +60,22 @@ class AssertThatBinaryExpressionIsTrueOrFalseInspection : AbstractAssertJInspect
val constantEvaluationHelper = JavaPsiFacade.getInstance(expression.project).constantEvaluationHelper
val swapExpectedAndActual = constantEvaluationHelper.computeConstantExpression(binaryExpression.lOperand) != null
val tokenType = binaryExpression.operationSign.tokenType.let {
if (swapExpectedAndActual) SWAP_BINARY_OPERATOR.getOrDefault(it, it) else it
} ?: return
val tokenType = binaryExpression.operationSign.tokenType
.let {
if (swapExpectedAndActual) SWAP_SIDE_OF_BINARY_OPERATOR.getOrDefault(it, it) else it
}
.let {
if (expectedResult) it else INVERT_BINARY_OPERATOR.getOrDefault(it, it)
} ?: return
val mappingToUse =
if (isPrimitive || isNumericType) {
PRIMITIVE_MAPPINGS
TOKEN_TO_ASSERTJ_FOR_PRIMITIVE_MAP
} else {
OBJECT_MAPPINGS
TOKEN_TO_ASSERTJ_FOR_OBJECT_MAPPINGS
}
val mapping = mappingToUse.find { it.tokenType == tokenType } ?: return
val replacementMethod = if (isInverted) mapping.replacementInverted else mapping.replacement
val replacementMethod = mappingToUse[tokenType] ?: return
registerSplitBinaryExpressionMethod(holder, expression, replacementMethod, pickRightOperand = swapExpectedAndActual)
}
private fun getExpectedResult(expectedCallExpression: PsiMethodCallExpression): Boolean? {
val isTrue = IS_TRUE.test(expectedCallExpression)
val isFalse = IS_FALSE.test(expectedCallExpression)
if (isTrue || isFalse) {
return isFalse
} else {
val isEqualTo = IS_EQUAL_TO_BOOLEAN.test(expectedCallExpression)
val isNotEqualTo = IS_NOT_EQUAL_TO_BOOLEAN.test(expectedCallExpression)
if (isEqualTo || isNotEqualTo) {
val constValue = calculateConstantParameterValue(expectedCallExpression, 0) as? Boolean ?: return null
return isEqualTo xor constValue
}
}
return null
registerSplitBinaryExpressionMethod(holder, expression, "$replacementMethod()", pickRightOperand = swapExpectedAndActual)
}
private fun registerSplitBinaryExpressionMethod(
@ -130,10 +90,4 @@ class AssertThatBinaryExpressionIsTrueOrFalseInspection : AbstractAssertJInspect
}
}
}
private class Mapping(
val tokenType: IElementType,
val replacement: String,
val replacementInverted: String
)
}

View File

@ -30,11 +30,11 @@ class AssertThatBooleanIsTrueOrFalseInspection : AbstractAssertJInspection() {
return
}
val equalToExpression = expression.firstArg
if (!TypeConversionUtil.isBooleanType(equalToExpression.type)) {
val expectedExpression = expression.firstArg
if (!TypeConversionUtil.isBooleanType(expectedExpression.type)) {
return
}
val expectedResult = calculateConstantParameterValue(expression, 0)as? Boolean ?: return
val expectedResult = calculateConstantParameterValue(expression, 0) as? Boolean ?: return
val flippedBooleanTest = matchingCalls.drop(2).any { it }
val replacementMethod = if (expectedResult xor flippedBooleanTest) "isTrue()" else "isFalse()"

View File

@ -0,0 +1,65 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.JavaElementVisitor
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.PsiStatement
import com.intellij.psi.util.PsiTreeUtil
import de.platon42.intellij.plugins.cajon.firstArg
class AssertThatJava8OptionalInspection : AbstractAssertJInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting an Optional (Java 8)"
}
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_ANY.test(expression)) {
return
}
val statement = PsiTreeUtil.getParentOfType(expression, PsiStatement::class.java) ?: return
val expectedCallExpression = PsiTreeUtil.findChildOfType(statement, PsiMethodCallExpression::class.java) ?: return
if (ASSERT_THAT_JAVA8_OPTIONAL.test(expression)) {
if (IS_EQUAL_TO_OBJECT.test(expectedCallExpression)) {
val innerExpectedCall = expectedCallExpression.firstArg as? PsiMethodCallExpression ?: return
if (OPTIONAL_OF.test(innerExpectedCall) || OPTIONAL_OF_NULLABLE.test(innerExpectedCall)) {
registerRemoveExpectedOutmostMethod(holder, expression, expectedCallExpression, "contains()")
} else if (OPTIONAL_EMPTY.test(innerExpectedCall)) {
registerSimplifyMethod(holder, expectedCallExpression, "isNotPresent()")
}
} else if (IS_NOT_EQUAL_TO_OBJECT.test(expectedCallExpression)) {
val innerExpectedCall = expectedCallExpression.firstArg as? PsiMethodCallExpression ?: return
if (OPTIONAL_EMPTY.test(innerExpectedCall)) {
registerSimplifyMethod(holder, expectedCallExpression, "isPresent()")
}
}
} else {
val actualExpression = expression.firstArg as? PsiMethodCallExpression ?: return
val isGet = OPTIONAL_GET.test(actualExpression)
val isIsPresent = OPTIONAL_IS_PRESENT.test(actualExpression)
if (isGet || isIsPresent) {
if (isGet) {
if (IS_EQUAL_TO_OBJECT.test(expectedCallExpression)) {
registerRemoveActualOutmostMethod(holder, expression, expectedCallExpression, "contains()")
} else if (IS_SAME_AS_OBJECT.test(expectedCallExpression)) {
registerRemoveActualOutmostMethod(holder, expression, expectedCallExpression, "containsSame()")
}
} else {
val expectedPresence = getExpectedBooleanResult(expectedCallExpression) ?: return
val replacementMethod = if (expectedPresence) "isPresent()" else "isNotPresent()"
registerRemoveActualOutmostMethod(holder, expression, expectedCallExpression, replacementMethod, noExpectedExpression = true)
}
}
}
}
}
}
}

View File

@ -10,7 +10,12 @@ class AssertThatSizeInspection : AbstractAssertJInspection() {
companion object {
private const val DISPLAY_NAME = "Asserting the size of an collection or array"
private const val MORE_CONCISE_MESSAGE_TEMPLATE = "%s would be more concise than %s"
private val BONUS_EXPRESSIONS_CALL_MATCHER_MAP = listOf(
IS_GREATER_THAN_INT to "hasSizeGreaterThan()",
IS_GREATER_THAN_OR_EQUAL_TO_INT to "hasSizeGreaterThanOrEqualTo()",
IS_LESS_THAN_OR_EQUAL_TO_INT to "hasSizeLessThanOrEqualTo()",
IS_LESS_THAN_INT to "hasSizeLessThan()"
)
}
override fun getDisplayName() = DISPLAY_NAME
@ -30,41 +35,29 @@ class AssertThatSizeInspection : AbstractAssertJInspection() {
val constValue = calculateConstantParameterValue(expectedCallExpression, 0)
if (IS_EQUAL_TO_INT.test(expectedCallExpression)) {
if (constValue == 0) {
registerSizeMethod(holder, expression, expectedCallExpression, "isEmpty()", noExpectedExpression = true)
return
registerReplaceSizeMethod(holder, expression, expectedCallExpression, "isEmpty()", noExpectedExpression = true)
} else {
val equalToExpression = expectedCallExpression.firstArg
if (isCollectionSize(equalToExpression) || isArrayLength(equalToExpression)) {
registerReplaceSizeMethod(holder, expression, expectedCallExpression, "hasSameSizeAs()", expectedIsCollection = true)
} else {
registerReplaceSizeMethod(holder, expression, expectedCallExpression, "hasSize()")
}
}
val equalToExpression = expectedCallExpression.firstArg
if (isCollectionSize(equalToExpression) || isArrayLength(equalToExpression)) {
registerSizeMethod(holder, expression, expectedCallExpression, "hasSameSizeAs()", expectedIsCollection = true)
return
}
registerSizeMethod(holder, expression, expectedCallExpression, "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, expectedCallExpression, "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, expectedCallExpression, "isNotEmpty()", noExpectedExpression = true)
return
}
// new stuff in AssertJ 13.2.0
if (hasAssertJMethod(expression, "AbstractIterableAssert.hasSizeLessThan")) {
val matchedMethod = listOf(
IS_GREATER_THAN_INT to "hasSizeGreaterThan()",
IS_GREATER_THAN_OR_EQUAL_TO_INT to "hasSizeGreaterThanOrEqualTo()",
IS_LESS_THAN_OR_EQUAL_TO_INT to "hasSizeLessThanOrEqualTo()",
IS_LESS_THAN_INT to "hasSizeLessThan()"
).find { it.first.test(expectedCallExpression) }?.second
if (matchedMethod != null) {
registerSizeMethod(holder, expression, expectedCallExpression, matchedMethod)
return
val isTestForEmpty = ((IS_LESS_THAN_OR_EQUAL_TO_INT.test(expectedCallExpression) && (constValue == 0))
|| (IS_LESS_THAN_INT.test(expectedCallExpression) && (constValue == 1))
|| IS_ZERO.test(expectedCallExpression))
val isTestForNotEmpty = ((IS_GREATER_THAN_INT.test(expectedCallExpression) && (constValue == 0))
|| (IS_GREATER_THAN_OR_EQUAL_TO_INT.test(expectedCallExpression) && (constValue == 1))
|| IS_NOT_ZERO.test(expectedCallExpression))
when {
isTestForEmpty -> registerReplaceSizeMethod(holder, expression, expectedCallExpression, "isEmpty()", noExpectedExpression = true)
isTestForNotEmpty -> registerReplaceSizeMethod(holder, expression, expectedCallExpression, "isNotEmpty()", noExpectedExpression = true)
// new stuff in AssertJ 13.2.0
hasAssertJMethod(expression, "AbstractIterableAssert.hasSizeLessThan") -> {
val matchedMethod = BONUS_EXPRESSIONS_CALL_MATCHER_MAP.find { it.first.test(expectedCallExpression) }?.second ?: return
registerReplaceSizeMethod(holder, expression, expectedCallExpression, matchedMethod)
}
}
}
@ -79,7 +72,7 @@ class AssertThatSizeInspection : AbstractAssertJInspection() {
&& ((psiReferenceExpression.resolve() as? PsiField)?.name == "length"))
}
private fun registerSizeMethod(
private fun registerReplaceSizeMethod(
holder: ProblemsHolder,
expression: PsiMethodCallExpression,
expectedCallExpression: PsiMethodCallExpression,

View File

@ -0,0 +1,36 @@
package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.PsiStatement
import com.intellij.psi.util.PsiTreeUtil
import de.platon42.intellij.plugins.cajon.firstArg
import de.platon42.intellij.plugins.cajon.qualifierExpression
import de.platon42.intellij.plugins.cajon.replaceQualifierFromMethodCall
class RemoveActualOutmostMethodCallQuickFix(
description: String,
private val replacementMethod: String,
private val noExpectedExpression: Boolean
) : AbstractCommonQuickFix(description) {
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.startElement
val methodCallExpression = element as? PsiMethodCallExpression ?: return
val assertExpression = methodCallExpression.firstArg as? PsiMethodCallExpression ?: return
assertExpression.replace(assertExpression.qualifierExpression)
val statement = PsiTreeUtil.getParentOfType(element, PsiStatement::class.java) ?: return
val oldExpectedExpression = PsiTreeUtil.findChildOfType(statement, PsiMethodCallExpression::class.java) ?: return
val factory = JavaPsiFacade.getElementFactory(element.project)
val expectedExpression =
factory.createExpressionFromText("a.${if (noExpectedExpression) replacementMethod else replacementMethod.replace("()", "(e)")}", element) as PsiMethodCallExpression
if (!noExpectedExpression) {
expectedExpression.firstArg.replace(oldExpectedExpression.firstArg)
}
expectedExpression.replaceQualifierFromMethodCall(oldExpectedExpression)
oldExpectedExpression.replace(expectedExpression)
}
}

View File

@ -0,0 +1,27 @@
package de.platon42.intellij.plugins.cajon.quickfixes
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.PsiStatement
import com.intellij.psi.util.PsiTreeUtil
import de.platon42.intellij.plugins.cajon.firstArg
import de.platon42.intellij.plugins.cajon.replaceQualifierFromMethodCall
class ReplaceExpectedOutmostMethodCallQuickFix(description: String, private val replacementMethod: String) : AbstractCommonQuickFix(description) {
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.startElement
val statement = PsiTreeUtil.getParentOfType(element, PsiStatement::class.java) ?: return
val oldExpectedExpression = PsiTreeUtil.findChildOfType(statement, PsiMethodCallExpression::class.java) ?: return
val factory = JavaPsiFacade.getElementFactory(element.project)
val expectedExpression =
factory.createExpressionFromText("a.${replacementMethod.replace("()", "(e)")}", element) as PsiMethodCallExpression
val expectedMethodCallExpression = oldExpectedExpression.firstArg as? PsiMethodCallExpression ?: return
expectedExpression.firstArg.replace(expectedMethodCallExpression.firstArg)
expectedExpression.replaceQualifierFromMethodCall(oldExpectedExpression)
oldExpectedExpression.replace(expectedExpression)
}
}

View File

@ -34,6 +34,9 @@
<localInspection groupPath="Java" shortName="AssertThatBinaryExpressionIsTrueOrFalse" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatBinaryExpressionIsTrueOrFalseInspection"/>
<localInspection groupPath="Java" shortName="AssertThatJava8Optional" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.AssertThatJava8OptionalInspection"/>
<localInspection groupPath="Java" shortName="JUnitAssertToAssertJ" enabledByDefault="true" level="WARNING"
implementationClass="de.platon42.intellij.plugins.cajon.inspections.JUnitAssertToAssertJInspection"/>
</extensions>

View File

@ -0,0 +1,7 @@
<html>
<body>
Looks at expected and actual expression being of Java 8 Optional type and whether the statement effectively tries to assert the
presence, absence or content and then replaces the statement by isPresent(), isNotPresent(), contains() or containsSame().
<!-- tooltip end -->
</body>
</html>

View File

@ -129,27 +129,57 @@ public class Playground {
}
private void java8Optional() {
Optional<String> foo = Optional.empty();
assertThat(foo.get()).isEqualTo("bla");
assertThat(foo).contains("bla");
assertThat(foo.isPresent()).isTrue();
assertThat(!foo.isPresent()).isFalse();
assertThat(foo).isPresent();
assertThat(foo.isPresent()).isFalse();
assertThat(!foo.isPresent()).isTrue();
assertThat(foo).isNotPresent();
Optional<String> opt = Optional.empty();
assertThat(opt.isPresent()).isEqualTo(true);
assertThat(opt.isPresent()).isEqualTo(Boolean.TRUE);
assertThat(opt.isPresent()).isNotEqualTo(false);
assertThat(opt.isPresent()).isNotEqualTo(Boolean.FALSE);
assertThat(opt.isPresent()).isTrue();
assertThat(opt.isPresent()).isEqualTo(false);
assertThat(opt.isPresent()).isEqualTo(Boolean.FALSE);
assertThat(opt.isPresent()).isNotEqualTo(true);
assertThat(opt.isPresent()).isNotEqualTo(Boolean.TRUE);
assertThat(opt.isPresent()).isFalse();
assertThat(opt.get()).isEqualTo("foo");
assertThat(opt.get()).isSameAs("foo");
assertThat(opt).isEqualTo(Optional.of("foo"));
assertThat(opt).isEqualTo(Optional.ofNullable("foo"));
assertThat(opt).isNotEqualTo(Optional.of("foo"));
assertThat(opt).isNotEqualTo(Optional.ofNullable("foo"));
assertThat(opt).isEqualTo(Optional.empty());
assertThat(opt).isNotEqualTo(Optional.empty());
}
private void guavaOptional() {
com.google.common.base.Optional<String> foo = com.google.common.base.Optional.absent();
assertThat(foo.get()).isEqualTo("bla");
assertThat(foo).contains("bla");
assertThat(foo.isPresent()).isTrue();
assertThat(!foo.isPresent()).isFalse();
assertThat(foo).isPresent();
assertThat(foo.isPresent()).isFalse();
assertThat(!foo.isPresent()).isTrue();
assertThat(foo).isAbsent();
com.google.common.base.Optional<String> opt = com.google.common.base.Optional.absent();
assertThat(opt.isPresent()).isEqualTo(true);
assertThat(opt.isPresent()).isEqualTo(Boolean.TRUE);
assertThat(opt.isPresent()).isNotEqualTo(false);
assertThat(opt.isPresent()).isNotEqualTo(Boolean.FALSE);
assertThat(opt.isPresent()).isTrue();
assertThat(opt.isPresent()).isEqualTo(false);
assertThat(opt.isPresent()).isEqualTo(Boolean.FALSE);
assertThat(opt.isPresent()).isNotEqualTo(true);
assertThat(opt.isPresent()).isNotEqualTo(Boolean.TRUE);
assertThat(opt.isPresent()).isFalse();
assertThat(opt.get()).isEqualTo("foo");
assertThat(opt.get()).isSameAs("foo");
assertThat(opt).isEqualTo(com.google.common.base.Optional.of("foo"));
assertThat(opt).isEqualTo(com.google.common.base.Optional.fromNullable("foo"));
assertThat(opt).isNotEqualTo(com.google.common.base.Optional.of("foo"));
assertThat(opt).isNotEqualTo(com.google.common.base.Optional.fromNullable("foo"));
assertThat(opt).isEqualTo(com.google.common.base.Optional.absent());
assertThat(opt).isNotEqualTo(com.google.common.base.Optional.absent());
}
private void junitAssertions() {

View File

@ -14,7 +14,7 @@ internal class AssertThatBinaryExpressionIsTrueOrFalseInspectionTest : AbstractC
runTest {
myFixture.enableInspections(AssertThatBinaryExpressionIsTrueOrFalseInspection::class.java)
myFixture.configureByFile("BinaryExpressionBefore.java")
executeQuickFixes(myFixture, Regex.fromLiteral("Split binary expression out of assertThat()"), 144)
executeQuickFixes(myFixture, Regex.fromLiteral("Split binary expression out of assertThat()"), 148)
executeQuickFixes(myFixture, Regex.fromLiteral("Split equals() expression out of assertThat()"), 12)
myFixture.checkResultByFile("BinaryExpressionAfter.java")
}

View File

@ -0,0 +1,28 @@
package de.platon42.intellij.plugins.cajon.inspections
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import de.platon42.intellij.jupiter.MyFixture
import de.platon42.intellij.jupiter.TestDataSubPath
import de.platon42.intellij.plugins.cajon.AbstractCajonTest
import org.junit.jupiter.api.Test
internal class AssertThatJava8OptionalInspectionTest : AbstractCajonTest() {
@Test
@TestDataSubPath("inspections/AssertThatJava8Optional")
internal fun assertThat_get_or_isPresent_for_Java8_Optional_can_be_simplified(@MyFixture myFixture: JavaCodeInsightTestFixture) {
runTest {
myFixture.enableInspections(AssertThatJava8OptionalInspection::class.java)
myFixture.configureByFile("AssertThatJava8OptionalBefore.java")
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with isPresent()"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isNotEqualTo() with isPresent()"), 3)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with isNotPresent()"), 3)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isNotEqualTo() with isNotPresent()"), 2)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isTrue() with isPresent()"), 1)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isFalse() with isNotPresent()"), 1)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isEqualTo() with contains()"), 3)
executeQuickFixes(myFixture, Regex.fromLiteral("Replace isSameAs() with containsSame()"), 1)
myFixture.checkResultByFile("AssertThatJava8OptionalAfter.java")
}
}
}

View File

@ -0,0 +1,35 @@
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
public class AssertThatJava8Optional {
private void assertThatJava8Optional() {
Optional<String> opt = Optional.empty();
assertThat(opt).isPresent();
assertThat(opt).isPresent();
assertThat(opt).isPresent();
assertThat(opt).isPresent();
assertThat(opt).isPresent();
assertThat(opt).isNotPresent();
assertThat(opt).isNotPresent();
assertThat(opt).isNotPresent();
assertThat(opt).isNotPresent();
assertThat(opt).isNotPresent();
assertThat(opt).contains("foo");
assertThat(opt).containsSame("foo");
assertThat(opt.get()).isNotEqualTo("foo");
assertThat(opt.get()).isNotSameAs("foo");
assertThat(opt).contains("foo");
assertThat(opt).contains("foo");
assertThat(opt).isNotEqualTo(Optional.of("foo"));
assertThat(opt).isNotEqualTo(Optional.ofNullable("foo"));
assertThat(opt).isNotPresent();
assertThat(opt).isPresent();
}
}

View File

@ -0,0 +1,35 @@
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
public class AssertThatJava8Optional {
private void assertThatJava8Optional() {
Optional<String> opt = Optional.empty();
assertThat(opt.isPresent()).isEqualTo(true);
assertThat(opt.isPresent()).isEqualTo(Boolean.TRUE);
assertThat(opt.isPresent()).isNotEqualTo(false);
assertThat(opt.isPresent()).isNotEqualTo(Boolean.FALSE);
assertThat(opt.isPresent()).isTrue();
assertThat(opt.isPresent()).isEqualTo(false);
assertThat(opt.isPresent()).isEqualTo(Boolean.FALSE);
assertThat(opt.isPresent()).isNotEqualTo(true);
assertThat(opt.isPresent()).isNotEqualTo(Boolean.TRUE);
assertThat(opt.isPresent()).isFalse();
assertThat(opt.get()).isEqualTo("foo");
assertThat(opt.get()).isSameAs("foo");
assertThat(opt.get()).isNotEqualTo("foo");
assertThat(opt.get()).isNotSameAs("foo");
assertThat(opt).isEqualTo(Optional.of("foo"));
assertThat(opt).isEqualTo(Optional.ofNullable("foo"));
assertThat(opt).isNotEqualTo(Optional.of("foo"));
assertThat(opt).isNotEqualTo(Optional.ofNullable("foo"));
assertThat(opt).isEqualTo(Optional.empty());
assertThat(opt).isNotEqualTo(Optional.empty());
}
}

View File

@ -10,6 +10,8 @@ public class BinaryExpression {
String stringExp = "foo";
String stringAct = "bar";
assertThat(primAct).isEqualTo(primExp);
assertThat(primAct).isEqualTo(primExp);
assertThat(primAct).isEqualTo(primExp);
assertThat(primAct).isEqualTo(primExp);
assertThat(primAct).isEqualTo(primExp);
@ -18,6 +20,8 @@ public class BinaryExpression {
assertThat(primAct).isNotEqualTo(primExp);
assertThat(primAct).isNotEqualTo(primExp);
assertThat(primAct).isNotEqualTo(primExp);
assertThat(primAct).isNotEqualTo(primExp);
assertThat(primAct).isNotEqualTo(primExp);
assertThat(primAct).isNotEqualTo(1);
assertThat(primAct).isNotEqualTo(1);

View File

@ -12,12 +12,16 @@ public class BinaryExpression {
assertThat(primAct == primExp).isTrue();
assertThat(primAct == primExp).isEqualTo(true);
assertThat(primAct == primExp).isEqualTo(Boolean.TRUE);
assertThat(primAct == primExp).isNotEqualTo(false);
assertThat(primAct == primExp).isNotEqualTo(Boolean.FALSE);
assertThat(primAct == 1).isTrue();
assertThat(1 == primAct).isTrue();
assertThat(primAct == primExp).isFalse();
assertThat(primAct == primExp).isEqualTo(false);
assertThat(primAct == primExp).isEqualTo(Boolean.FALSE);
assertThat(primAct == primExp).isNotEqualTo(true);
assertThat(primAct == primExp).isNotEqualTo(Boolean.TRUE);
assertThat(primAct == 1).isFalse();
assertThat(1 == primAct).isFalse();