Port InfixCallToOrdinaryIntention to K2 (make shared) and turn it into inspection

^KTIJ-22973 fixed

Merge-request: IJ-MR-128762
Merged-by: Victoria Petrakovich <Victoria.Petrakovich@jetbrains.com>

GitOrigin-RevId: d6c72864321a6aaf34937ae8cf2bb7bdbb85880e
This commit is contained in:
Victoria.Petrakovich
2024-03-14 16:39:04 +00:00
committed by intellij-monorepo-bot
parent 57cc908f37
commit 86b35bceaa
29 changed files with 161 additions and 132 deletions
@@ -10,6 +10,7 @@ group.names.other.problems=Other problems
group.names.probable.bugs=Probable bugs
group.names.redundant.constructs=Redundant constructs
group.names.style.issues=Style issues
group.names.code.migration=Code migration
fix.insert.delegation.call=Insert ''{0}()'' call
fix.introduce.non.null.assertion=Add non-null asserted (!!) call
fix.remove.non.null.assertion=Remove unnecessary non-null assertion (!!)
@@ -1788,6 +1789,7 @@ add.import.for.0=Add import for ''{0}''
add.import.for.member=Add import for member
indent.raw.string=Indent raw string
replace.infix.call.with.ordinary.call=Replace infix call with ordinary call
infix.call.may.be.dot.call=Infix call may be dot call
insert.curly.braces.around.variable=Insert curly braces around variable
add.explicit.type.arguments=Add explicit type arguments
introduce.backing.property=Introduce backing property
@@ -0,0 +1,12 @@
<html>
<body>
Reports for infix function calls that can be replaced with dot-qualified function calls.
<p>Example:</p>
<pre><code>
1 xor 2
</code></pre>
<pre><code>
1.xor(2)
</code></pre>
</body>
</html>
@@ -1,5 +0,0 @@
<html>
<body>
Converts an infix function call to a dot-qualified function call.
</body>
</html>
@@ -331,5 +331,13 @@
language="kotlin"
key="inspection.redundant.labeled.return.on.last.expression.in.lambda.display.name" bundle="messages.KotlinBundle"/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.codeInsight.inspections.shared.InfixCallToOrdinaryInspection"
groupPath="Kotlin"
groupBundle="messages.KotlinBundle" groupKey="group.names.code.migration"
enabledByDefault="true"
level="INFORMATION"
language="kotlin"
key="infix.call.may.be.dot.call" bundle="messages.KotlinBundle"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,58 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.codeInsight.inspections.shared
import com.intellij.codeInspection.LocalInspectionToolSession
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.modcommand.ModPsiUpdater
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.codeinsight.api.applicable.inspections.AbstractKotlinApplicableInspection
import org.jetbrains.kotlin.idea.codeinsight.api.applicators.KotlinApplicabilityRange
import org.jetbrains.kotlin.idea.codeinsight.api.applicators.applicabilityRange
import org.jetbrains.kotlin.idea.codeinsights.impl.base.applicators.ApplicabilityRanges
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.startOffset
internal class InfixCallToOrdinaryInspection : AbstractKotlinApplicableInspection<KtBinaryExpression>() {
override fun getProblemDescription(element: KtBinaryExpression) = KotlinBundle.message("replace.infix.call.with.ordinary.call")
override fun apply(element: KtBinaryExpression, project: Project, updater: ModPsiUpdater) {
convertInfixCallToOrdinary(element)
}
override fun getActionFamilyName() = KotlinBundle.message("replace.infix.call.with.ordinary.call")
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor =
binaryExpressionVisitor {
visitTargetElement(it, holder, isOnTheFly)
}
override fun getActionName(element: KtBinaryExpression): String = KotlinBundle.message("replace.infix.call.with.ordinary.call")
override fun getApplicabilityRange(): KotlinApplicabilityRange<KtBinaryExpression> = applicabilityRange {
it.operationReference.textRangeInParent
}
override fun isApplicableByPsi(element: KtBinaryExpression): Boolean {
return !(element.operationToken != KtTokens.IDENTIFIER || element.left == null || element.right == null)
}
}
fun convertInfixCallToOrdinary(element: KtBinaryExpression): KtExpression {
val argument = KtPsiUtil.safeDeparenthesize(element.right!!)
val pattern = "$0.$1" + when (argument) {
is KtLambdaExpression -> " $2:'{}'"
else -> "($2)"
}
val replacement = KtPsiFactory(element.project).createExpressionByPattern(
pattern,
element.left!!,
element.operationReference,
argument
)
return element.replace(replacement) as KtExpression
}
@@ -428,6 +428,44 @@ public abstract class SharedK1LocalInspectionTestGenerated extends AbstractShare
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../testData/inspectionsLocal/infixCallToOrdinary")
public static class InfixCallToOrdinary extends AbstractSharedK1LocalInspectionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("functionCallAfterInfixCall.kt")
public void testFunctionCallAfterInfixCall() throws Exception {
runTest("../testData/inspectionsLocal/infixCallToOrdinary/functionCallAfterInfixCall.kt");
}
@TestMetadata("functionLiteralArgument.kt")
public void testFunctionLiteralArgument() throws Exception {
runTest("../testData/inspectionsLocal/infixCallToOrdinary/functionLiteralArgument.kt");
}
@TestMetadata("nonApplicableBinaryOperation.kt")
public void testNonApplicableBinaryOperation() throws Exception {
runTest("../testData/inspectionsLocal/infixCallToOrdinary/nonApplicableBinaryOperation.kt");
}
@TestMetadata("nullAssertedCall.kt")
public void testNullAssertedCall() throws Exception {
runTest("../testData/inspectionsLocal/infixCallToOrdinary/nullAssertedCall.kt");
}
@TestMetadata("parenthesesAroundRightHandArgument.kt")
public void testParenthesesAroundRightHandArgument() throws Exception {
runTest("../testData/inspectionsLocal/infixCallToOrdinary/parenthesesAroundRightHandArgument.kt");
}
@TestMetadata("simpleInfixFunctionCall.kt")
public void testSimpleInfixFunctionCall() throws Exception {
runTest("../testData/inspectionsLocal/infixCallToOrdinary/simpleInfixFunctionCall.kt");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../testData/inspectionsLocal/javaIoSerializableObjectMustHaveReadResolve")
public static class JavaIoSerializableObjectMustHaveReadResolve extends AbstractSharedK1LocalInspectionTest {
@@ -428,6 +428,44 @@ public abstract class SharedK2LocalInspectionTestGenerated extends AbstractShare
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../testData/inspectionsLocal/infixCallToOrdinary")
public static class InfixCallToOrdinary extends AbstractSharedK2LocalInspectionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("functionCallAfterInfixCall.kt")
public void testFunctionCallAfterInfixCall() throws Exception {
runTest("../testData/inspectionsLocal/infixCallToOrdinary/functionCallAfterInfixCall.kt");
}
@TestMetadata("functionLiteralArgument.kt")
public void testFunctionLiteralArgument() throws Exception {
runTest("../testData/inspectionsLocal/infixCallToOrdinary/functionLiteralArgument.kt");
}
@TestMetadata("nonApplicableBinaryOperation.kt")
public void testNonApplicableBinaryOperation() throws Exception {
runTest("../testData/inspectionsLocal/infixCallToOrdinary/nonApplicableBinaryOperation.kt");
}
@TestMetadata("nullAssertedCall.kt")
public void testNullAssertedCall() throws Exception {
runTest("../testData/inspectionsLocal/infixCallToOrdinary/nullAssertedCall.kt");
}
@TestMetadata("parenthesesAroundRightHandArgument.kt")
public void testParenthesesAroundRightHandArgument() throws Exception {
runTest("../testData/inspectionsLocal/infixCallToOrdinary/parenthesesAroundRightHandArgument.kt");
}
@TestMetadata("simpleInfixFunctionCall.kt")
public void testSimpleInfixFunctionCall() throws Exception {
runTest("../testData/inspectionsLocal/infixCallToOrdinary/simpleInfixFunctionCall.kt");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../testData/inspectionsLocal/javaIoSerializableObjectMustHaveReadResolve")
public static class JavaIoSerializableObjectMustHaveReadResolve extends AbstractSharedK2LocalInspectionTest {
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.codeInsight.inspections.shared.InfixCallToOrdinaryInspection
@@ -1,31 +0,0 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.kotlin.idea.inspections
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.codeInspection.IntentionWrapper
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticWithParameters2
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.intentions.InfixCallToOrdinaryIntention
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
import org.jetbrains.kotlin.idea.refactoring.canRefactor
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtModifierListOwner
import org.jetbrains.kotlin.psi.KtOperationReferenceExpression
object InfixCallFixActionFactory : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val functionDescriptor = (diagnostic as? DiagnosticWithParameters2<*, *, *>)?.a as? FunctionDescriptor ?: return null
val target = DescriptorToSourceUtilsIde.getAnyDeclaration(diagnostic.psiFile.project, functionDescriptor)
as? KtModifierListOwner
if (target == null || target.canRefactor()) {
// we'll fix the problem by adding the 'infix' modifier to the target
return null
}
if ((diagnostic.psiElement as? KtOperationReferenceExpression)?.parent !is KtBinaryExpression) return null
return IntentionWrapper(InfixCallToOrdinaryIntention())
}
}
@@ -1,42 +0,0 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.codeinsight.api.classic.intentions.SelfTargetingIntention
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
class InfixCallToOrdinaryIntention : SelfTargetingIntention<KtBinaryExpression>(
KtBinaryExpression::class.java,
KotlinBundle.lazyMessage("replace.infix.call.with.ordinary.call")
) {
override fun isApplicableTo(element: KtBinaryExpression, caretOffset: Int): Boolean {
if (element.operationToken != KtTokens.IDENTIFIER || element.left == null || element.right == null) return false
return element.operationReference.textRange.containsOffset(caretOffset)
}
override fun applyTo(element: KtBinaryExpression, editor: Editor?) {
Holder.convert(element)
}
object Holder {
fun convert(element: KtBinaryExpression): KtExpression {
val argument = KtPsiUtil.safeDeparenthesize(element.right!!)
val pattern = "$0.$1" + when (argument) {
is KtLambdaExpression -> " $2:'{}'"
else -> "($2)"
}
val replacement = KtPsiFactory(element.project).createExpressionByPattern(
pattern,
element.left!!,
element.operationReference.text,
argument
)
return element.replace(replacement) as KtExpression
}
}
}
@@ -9,7 +9,6 @@ import org.jetbrains.kotlin.idea.codeinsights.impl.base.quickFix.ChangeVariableM
import org.jetbrains.kotlin.idea.core.overrideImplement.ImplementAsConstructorParameter
import org.jetbrains.kotlin.idea.core.overrideImplement.ImplementMembersHandler
import org.jetbrains.kotlin.idea.inspections.AddModifierFixFactory
import org.jetbrains.kotlin.idea.inspections.InfixCallFixActionFactory
import org.jetbrains.kotlin.idea.inspections.RemoveAnnotationFix
import org.jetbrains.kotlin.idea.intentions.*
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createCallable.*
@@ -549,7 +548,6 @@ class QuickFixRegistrar : QuickFixContributor {
OPERATOR_MODIFIER_REQUIRED.registerFactory(ImportForMissingOperatorFactory)
INFIX_MODIFIER_REQUIRED.registerFactory(AddModifierFixFactory(INFIX_KEYWORD))
INFIX_MODIFIER_REQUIRED.registerFactory(InfixCallFixActionFactory)
UNDERSCORE_IS_RESERVED.registerFactory(RenameUnderscoreFix)
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiUnificationResult.Str
import org.jetbrains.kotlin.idea.base.psi.unifier.KotlinPsiUnificationResult.WeakSuccess
import org.jetbrains.kotlin.idea.base.psi.unifier.toRange
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.codeInsight.inspections.shared.convertInfixCallToOrdinary
import org.jetbrains.kotlin.idea.codeinsights.impl.base.inspections.OperatorToFunctionConverter
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.appendElement
@@ -34,7 +35,6 @@ import org.jetbrains.kotlin.idea.core.moveInsideParenthesesAndReplaceWith
import org.jetbrains.kotlin.idea.core.toVisibility
import org.jetbrains.kotlin.idea.inspections.PublicApiImplicitTypeInspection
import org.jetbrains.kotlin.idea.inspections.UseExpressionBodyInspection
import org.jetbrains.kotlin.idea.intentions.InfixCallToOrdinaryIntention
import org.jetbrains.kotlin.idea.intentions.RemoveExplicitTypeArgumentsIntention
import org.jetbrains.kotlin.idea.refactoring.introduce.*
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.*
@@ -292,7 +292,7 @@ private fun makeCall(
val newNameExpression = when (val operationExpression = anchor.parent as? KtOperationExpression ?: return null) {
is KtUnaryExpression -> OperatorToFunctionConverter.convert(operationExpression).second
is KtBinaryExpression -> {
InfixCallToOrdinaryIntention.Holder.convert(operationExpression).getCalleeExpressionIfAny()
convertInfixCallToOrdinary(operationExpression).getCalleeExpressionIfAny()
}
else -> null
}
@@ -10949,44 +10949,6 @@ public abstract class K1IntentionTestGenerated extends AbstractK1IntentionTest {
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("testData/intentions/infixCallToOrdinary")
public static class InfixCallToOrdinary extends AbstractK1IntentionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("functionCallAfterInfixCall.kt")
public void testFunctionCallAfterInfixCall() throws Exception {
runTest("testData/intentions/infixCallToOrdinary/functionCallAfterInfixCall.kt");
}
@TestMetadata("functionLiteralArgument.kt")
public void testFunctionLiteralArgument() throws Exception {
runTest("testData/intentions/infixCallToOrdinary/functionLiteralArgument.kt");
}
@TestMetadata("nonApplicableBinaryOperation.kt")
public void testNonApplicableBinaryOperation() throws Exception {
runTest("testData/intentions/infixCallToOrdinary/nonApplicableBinaryOperation.kt");
}
@TestMetadata("nullAssertedCall.kt")
public void testNullAssertedCall() throws Exception {
runTest("testData/intentions/infixCallToOrdinary/nullAssertedCall.kt");
}
@TestMetadata("parenthesesAroundRightHandArgument.kt")
public void testParenthesesAroundRightHandArgument() throws Exception {
runTest("testData/intentions/infixCallToOrdinary/parenthesesAroundRightHandArgument.kt");
}
@TestMetadata("simpleInfixFunctionCall.kt")
public void testSimpleInfixFunctionCall() throws Exception {
runTest("testData/intentions/infixCallToOrdinary/simpleInfixFunctionCall.kt");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("testData/intentions/insertCurlyBracesToTemplate")
public static class InsertCurlyBracesToTemplate extends AbstractK1IntentionTest {
@@ -1 +0,0 @@
org.jetbrains.kotlin.idea.intentions.InfixCallToOrdinaryIntention
@@ -2,7 +2,7 @@
// ERROR: Unresolved reference: foo
// ACTION: Create extension function 'H.foo'
// ACTION: Create member function 'H.foo'
// ACTION: Replace infix call with ordinary call
// FIX: Replace infix call with ordinary call
package h
@@ -217,13 +217,6 @@
<categoryKey>group.names.kotlin</categoryKey>
</intentionAction>
<intentionAction>
<language>kotlin</language>
<className>org.jetbrains.kotlin.idea.intentions.InfixCallToOrdinaryIntention</className>
<bundleName>messages.KotlinBundle</bundleName>
<categoryKey>group.names.kotlin</categoryKey>
</intentionAction>
<intentionAction>
<language>kotlin</language>
<className>org.jetbrains.kotlin.idea.intentions.ToInfixCallIntention</className>