diff --git a/plugins/kotlin/j2k/k1.new.post-processing/src/org/jetbrains/kotlin/idea/j2k/post/processing/allProcessings.kt b/plugins/kotlin/j2k/k1.new.post-processing/src/org/jetbrains/kotlin/idea/j2k/post/processing/allProcessings.kt index 1dede0761cae..20a30bf67de2 100644 --- a/plugins/kotlin/j2k/k1.new.post-processing/src/org/jetbrains/kotlin/idea/j2k/post/processing/allProcessings.kt +++ b/plugins/kotlin/j2k/k1.new.post-processing/src/org/jetbrains/kotlin/idea/j2k/post/processing/allProcessings.kt @@ -112,7 +112,6 @@ private val inspectionLikePostProcessingGroup = InspectionLikeProcessingGroup( LiftAssignmentInspectionBasedProcessing(), MayBeConstantInspectionBasedProcessing(), RemoveForExpressionLoopParameterTypeProcessing(), - inspectionBasedProcessing(ReplaceGuardClauseWithFunctionCallInspection()), intentionBasedProcessing(ConvertToRawStringTemplateIntention(), additionalChecker = ::shouldConvertToRawString), intentionBasedProcessing(IndentRawStringIntention()), intentionBasedProcessing(JoinDeclarationAndAssignmentIntention()), diff --git a/plugins/kotlin/j2k/k1.new/tests/test/org/jetbrains/kotlin/nj2k/NewJavaToKotlinConverterSingleFileTestGenerated.java b/plugins/kotlin/j2k/k1.new/tests/test/org/jetbrains/kotlin/nj2k/NewJavaToKotlinConverterSingleFileTestGenerated.java index c81d30423d91..dae7c4a8a96c 100644 --- a/plugins/kotlin/j2k/k1.new/tests/test/org/jetbrains/kotlin/nj2k/NewJavaToKotlinConverterSingleFileTestGenerated.java +++ b/plugins/kotlin/j2k/k1.new/tests/test/org/jetbrains/kotlin/nj2k/NewJavaToKotlinConverterSingleFileTestGenerated.java @@ -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"); diff --git a/plugins/kotlin/j2k/k2/tests/test/org/jetbrains/kotlin/j2k/k2/K2JavaToKotlinConverterSingleFileTestGenerated.java b/plugins/kotlin/j2k/k2/tests/test/org/jetbrains/kotlin/j2k/k2/K2JavaToKotlinConverterSingleFileTestGenerated.java index 5c3e790211b7..091985341a2d 100644 --- a/plugins/kotlin/j2k/k2/tests/test/org/jetbrains/kotlin/j2k/k2/K2JavaToKotlinConverterSingleFileTestGenerated.java +++ b/plugins/kotlin/j2k/k2/tests/test/org/jetbrains/kotlin/j2k/k2/K2JavaToKotlinConverterSingleFileTestGenerated.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"); diff --git a/plugins/kotlin/j2k/shared/src/org/jetbrains/kotlin/nj2k/conversions/JavaStatementConversion.kt b/plugins/kotlin/j2k/shared/src/org/jetbrains/kotlin/nj2k/conversions/JavaStatementConversion.kt index 72da297f6d99..cbd09c6448ea 100644 --- a/plugins/kotlin/j2k/shared/src/org/jetbrains/kotlin/nj2k/conversions/JavaStatementConversion.kt +++ b/plugins/kotlin/j2k/shared/src/org/jetbrains/kotlin/nj2k/conversions/JavaStatementConversion.kt @@ -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()?.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) + } } diff --git a/plugins/kotlin/j2k/shared/tests/testData/newJ2k/postProcessing/GuardClause.kt b/plugins/kotlin/j2k/shared/tests/testData/newJ2k/postProcessing/GuardClause.kt index f7d77e8cc797..0261d578d428 100644 --- a/plugins/kotlin/j2k/shared/tests/testData/newJ2k/postProcessing/GuardClause.kt +++ b/plugins/kotlin/j2k/shared/tests/testData/newJ2k/postProcessing/GuardClause.kt @@ -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)) } diff --git a/plugins/kotlin/j2k/shared/tests/testData/newJ2k/postProcessing/GuardClauseBasicMode.java b/plugins/kotlin/j2k/shared/tests/testData/newJ2k/postProcessing/GuardClauseBasicMode.java new file mode 100644 index 000000000000..b899fe32d9c0 --- /dev/null +++ b/plugins/kotlin/j2k/shared/tests/testData/newJ2k/postProcessing/GuardClauseBasicMode.java @@ -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"); + } + } +} \ No newline at end of file diff --git a/plugins/kotlin/j2k/shared/tests/testData/newJ2k/postProcessing/GuardClauseBasicMode.kt b/plugins/kotlin/j2k/shared/tests/testData/newJ2k/postProcessing/GuardClauseBasicMode.kt new file mode 100644 index 000000000000..9d3f487ac766 --- /dev/null +++ b/plugins/kotlin/j2k/shared/tests/testData/newJ2k/postProcessing/GuardClauseBasicMode.kt @@ -0,0 +1,8 @@ +// !BASIC_MODE: true +class Test { + fun testBasicMode(s1: String?) { + if (s1 == null) { + throw IllegalArgumentException("s should not be null") + } + } +}