[kotlin] J2K: Port ReplaceGuardClauseWithFunctionCallInspection postprocessing to JKTree phase

KTIJ-28575

closes https://github.com/JetBrains/intellij-community/pull/2709

GitOrigin-RevId: f63831b7849c563f7f78fc7bd7b13b8e843dafbd
This commit is contained in:
Eve Matthaey
2024-03-25 19:18:55 +00:00
committed by intellij-monorepo-bot
parent 596e92645d
commit 79af0dbadd
7 changed files with 114 additions and 9 deletions
@@ -112,7 +112,6 @@ private val inspectionLikePostProcessingGroup = InspectionLikeProcessingGroup(
LiftAssignmentInspectionBasedProcessing(),
MayBeConstantInspectionBasedProcessing(),
RemoveForExpressionLoopParameterTypeProcessing(),
inspectionBasedProcessing(ReplaceGuardClauseWithFunctionCallInspection()),
intentionBasedProcessing(ConvertToRawStringTemplateIntention(), additionalChecker = ::shouldConvertToRawString),
intentionBasedProcessing(IndentRawStringIntention()),
intentionBasedProcessing(JoinDeclarationAndAssignmentIntention()),
@@ -5116,6 +5116,11 @@ public abstract class NewJavaToKotlinConverterSingleFileTestGenerated extends Ab
runTest("../../shared/tests/testData/newJ2k/postProcessing/GuardClause.java");
}
@TestMetadata("GuardClauseBasicMode.java")
public void testGuardClauseBasicMode() throws Exception {
runTest("../../shared/tests/testData/newJ2k/postProcessing/GuardClauseBasicMode.java");
}
@TestMetadata("IfNullReturnToElvis.java")
public void testIfNullReturnToElvis() throws Exception {
runTest("../../shared/tests/testData/newJ2k/postProcessing/IfNullReturnToElvis.java");
@@ -5116,6 +5116,11 @@ public abstract class K2JavaToKotlinConverterSingleFileTestGenerated extends Abs
runTest("../../shared/tests/testData/newJ2k/postProcessing/GuardClause.java");
}
@TestMetadata("GuardClauseBasicMode.java")
public void testGuardClauseBasicMode() throws Exception {
runTest("../../shared/tests/testData/newJ2k/postProcessing/GuardClauseBasicMode.java");
}
@TestMetadata("IfNullReturnToElvis.java")
public void testIfNullReturnToElvis() throws Exception {
runTest("../../shared/tests/testData/newJ2k/postProcessing/IfNullReturnToElvis.java");
@@ -5,9 +5,7 @@ import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.j2k.Nullability.NotNull
import org.jetbrains.kotlin.nj2k.*
import org.jetbrains.kotlin.nj2k.tree.*
import org.jetbrains.kotlin.nj2k.types.JKJavaDisjunctionType
import org.jetbrains.kotlin.nj2k.types.isNull
import org.jetbrains.kotlin.nj2k.types.updateNullability
import org.jetbrains.kotlin.nj2k.types.*
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class JavaStatementConversion(context: NewJ2kConverterContext) : RecursiveConversion(context) {
@@ -19,6 +17,7 @@ class JavaStatementConversion(context: NewJ2kConverterContext) : RecursiveConver
is JKJavaAssertStatement -> convertAssert(element)
is JKJavaSynchronizedStatement -> convertSynchronized(element)
is JKJavaTryStatement -> convertTry(element)
is JKIfElseStatement -> convertGuardStatement(element)
else -> element
}
)
@@ -36,7 +35,7 @@ class JavaStatementConversion(context: NewJ2kConverterContext) : RecursiveConver
if (element.description is JKStubExpression) null
else JKLambdaExpression(JKExpressionStatement(element::description.detached()))
val expressionComparedToNull = assertion.expressionComparedToNull()
val expressionComparedToNull = assertion.expressionComparedToNull(isNegated = true)
?: return kotlinAssert(assertion, messageExpression, symbolProvider).asStatement().withFormattingFrom(element)
val referencedVariable = (expressionComparedToNull as? JKFieldAccessExpression)?.identifier?.target as? JKLocalVariable
val checkNotNullSymbol = symbolProvider.provideMethodSymbol("kotlin.checkNotNull")
@@ -75,9 +74,10 @@ class JavaStatementConversion(context: NewJ2kConverterContext) : RecursiveConver
return statements.subList(declarationIndex + 1, expressionIndex).all { it.isEmpty() }
}
private fun JKExpression.expressionComparedToNull(): JKExpression? {
private fun JKExpression.expressionComparedToNull(isNegated: Boolean = false): JKExpression? {
if (this !is JKBinaryExpression) return null
if (operator.token.text != "!=") return null
if (isNegated && operator.token.text != "!=") return null
if (!isNegated && operator.token.text != "==") return null
val left = left
val right = right
@@ -167,4 +167,83 @@ class JavaStatementConversion(context: NewJ2kConverterContext) : RecursiveConver
tryStatement.catchSections.flatMap(::convertCatchSection)
)
).withFormattingFrom(tryStatement)
/**
* Replaces some if-then-throw statements with calls to `require` or `check`. For example, a statement like
* `if (enabled) throw new IllegalArgumentException("must be enabled")` would become `requireNotNull(s1) { "must be enabled" }`.
*
* This conversion is analogous to `ReplaceGuardClauseWithFunctionCallInspection` and is disabled in basic mode.
*/
private fun convertGuardStatement(ifElseStatement: JKIfElseStatement): JKStatement {
if (context.settings.basicMode) return ifElseStatement
val thenExpression = ifElseStatement.thenBranch.statements.singleOrNull() ?: return ifElseStatement
if (thenExpression !is JKExpressionStatement) return ifElseStatement
val thrownExpression = thenExpression.expression.safeAs<JKThrowExpression>()?.exception ?: return ifElseStatement
if (thrownExpression !is JKNewExpression || thrownExpression.arguments.arguments.size > 1) {
return ifElseStatement
}
val expressionComparedToNull = ifElseStatement.condition.expressionComparedToNull()
val exceptionName = thrownExpression.identifier?.name
val correspondingMethodName = when (exceptionName) {
"IllegalArgumentException" -> if (expressionComparedToNull != null) "kotlin.requireNotNull" else "kotlin.require"
"IllegalStateException" -> if (expressionComparedToNull != null) "kotlin.checkNotNull" else "kotlin.check"
else -> return ifElseStatement
}
val exceptionArgument = thrownExpression.arguments.arguments.firstOrNull()
if (exceptionArgument != null && exceptionArgument.value.calculateType(typeFactory)?.isStringType() != true) {
return ifElseStatement
}
val messageExpression = if (exceptionArgument == null) null else
JKLambdaExpression(JKExpressionStatement(exceptionArgument::value.detached()))
val methodCallSymbol = symbolProvider.provideMethodSymbol(correspondingMethodName)
val originalCondition = ifElseStatement::condition.detached()
val negatedCondition = if (originalCondition is JKPrefixExpression && originalCondition.operator.token.text == "!") {
val conditionExpression = originalCondition::expression.detached()
if (conditionExpression is JKParenthesizedExpression) {
// now that the `!` prefix has been removed, clean up any superfluous parentheses
conditionExpression::expression.detached()
} else {
conditionExpression
}
} else {
JKPrefixExpression(
originalCondition.parenthesizeIfCompoundExpression(),
JKKtOperatorImpl(JKOperatorToken.EXCL, typeFactory.types.boolean)
)
}.withFormattingFrom(originalCondition)
val newCallExpression = if (expressionComparedToNull != null && ifElseStatement.elseBranch.isEmpty()) {
JKCallExpressionImpl(
methodCallSymbol,
listOfNotNull(
expressionComparedToNull.detached(ifElseStatement.condition),
messageExpression
).toArgumentList()
)
} else {
JKCallExpressionImpl(
methodCallSymbol,
listOfNotNull(negatedCondition, messageExpression).toArgumentList()
)
}.asStatement()
val elseBranch = ifElseStatement::elseBranch.detached()
return if (elseBranch.isEmpty()) {
newCallExpression
} else {
val statements = if (elseBranch is JKBlockStatement) {
// any newlines that should follow the else block will be attached to the new parent block statement
elseBranch.statements.last().lineBreaksAfter = 0
listOf(newCallExpression) + elseBranch.statements.map { it.detached(elseBranch.block) }
} else {
listOf(newCallExpression, elseBranch)
}
JKBlockStatementWithoutBrackets(statements).withFormattingFrom(ifElseStatement)
}.withFormattingFrom(ifElseStatement)
}
}
@@ -1,5 +1,5 @@
class Test {
fun testRequire(s1: String?, b1: Boolean, b2: Boolean) {
fun testRequire(s1: String, b1: Boolean, b2: Boolean) {
requireNotNull(s1) { "s should not be null" }
require(b1)
@@ -27,7 +27,7 @@ class Test {
}
fun testDoubles(x: Double, y: Double) {
check((x < y))
check(x < y)
check(!(y < 2 * x))
}
@@ -0,0 +1,9 @@
// IGNORE_K2
// !BASIC_MODE: true
public class Test {
void testBasicMode(String s1) {
if (s1 == null) {
throw new IllegalArgumentException("s should not be null");
}
}
}
@@ -0,0 +1,8 @@
// !BASIC_MODE: true
class Test {
fun testBasicMode(s1: String?) {
if (s1 == null) {
throw IllegalArgumentException("s should not be null")
}
}
}