diff --git a/plugins/kotlin/code-insight/inspections-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/inspections/ReplaceGetOrSetInspection.kt b/plugins/kotlin/code-insight/inspections-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/inspections/ReplaceGetOrSetInspection.kt index 63c59a329aa9..1b0cda83b3b7 100644 --- a/plugins/kotlin/code-insight/inspections-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/inspections/ReplaceGetOrSetInspection.kt +++ b/plugins/kotlin/code-insight/inspections-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/inspections/ReplaceGetOrSetInspection.kt @@ -1,20 +1,28 @@ // Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.kotlin.idea.k2.codeinsight.inspections +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemHighlightType.GENERIC_ERROR_OR_WARNING +import com.intellij.codeInspection.ProblemHighlightType.INFORMATION import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.analysis.api.calls.KtSimpleFunctionCall import org.jetbrains.kotlin.analysis.api.calls.successfulCallOrNull import org.jetbrains.kotlin.analysis.api.calls.symbol +import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtVariableLikeSymbol +import org.jetbrains.kotlin.analysis.api.symbols.psiSafe import org.jetbrains.kotlin.idea.base.psi.textRangeIn import org.jetbrains.kotlin.idea.base.resources.KotlinBundle import org.jetbrains.kotlin.idea.codeinsight.api.applicable.inspections.AbstractKotlinApplicableInspectionWithContext import org.jetbrains.kotlin.idea.codeinsight.api.applicators.applicabilityRange import org.jetbrains.kotlin.idea.codeinsights.impl.base.inspections.ReplaceGetOrSetInspectionUtils +import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtDotQualifiedExpression +import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtSuperExpression import org.jetbrains.kotlin.psi.psiUtil.getPossiblyQualifiedCallExpression import org.jetbrains.kotlin.util.OperatorNameConventions @@ -24,7 +32,7 @@ internal class ReplaceGetOrSetInspection : KtDotQualifiedExpression::class ) { - class Context(val calleeName: Name) + class Context(val calleeName: Name, val problemHighlightType: ProblemHighlightType) override fun getProblemDescription(element: KtDotQualifiedExpression, context: Context): String = KotlinBundle.message("explicit.0.call", context.calleeName) @@ -38,6 +46,9 @@ internal class ReplaceGetOrSetInspection : dotQualifiedExpression.getPossiblyQualifiedCallExpression()?.calleeExpression?.textRangeIn(dotQualifiedExpression) } + override fun getProblemHighlightType(element: KtDotQualifiedExpression, context: Context): ProblemHighlightType = + context.problemHighlightType + override fun isApplicableByPsi(element: KtDotQualifiedExpression): Boolean = ReplaceGetOrSetInspectionUtils.looksLikeGetOrSetOperatorCall(element) @@ -57,7 +68,10 @@ internal class ReplaceGetOrSetInspection : element.getPossiblyQualifiedCallExpression()?.getKtType()?.isUnit != true && element.isUsedAsExpression() ) return null - return Context(functionSymbol.name) + + val problemHighlightType = if (functionSymbol.isExplicitOperator()) GENERIC_ERROR_OR_WARNING else INFORMATION + + return Context(functionSymbol.name, problemHighlightType) } override fun apply(element: KtDotQualifiedExpression, context: Context, project: Project, editor: Editor?) { @@ -67,4 +81,10 @@ internal class ReplaceGetOrSetInspection : editor ) } + + context(KtAnalysisSession) + private fun KtFunctionSymbol.isExplicitOperator(): Boolean { + fun KtCallableSymbol.hasOperatorKeyword() = psiSafe()?.hasModifier(KtTokens.OPERATOR_KEYWORD) == true + return hasOperatorKeyword() || getAllOverriddenSymbols().any { it.hasOperatorKeyword() } + } } \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java b/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java index d920b1d42e36..93901328b850 100644 --- a/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java +++ b/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java @@ -505,6 +505,21 @@ public abstract class K2LocalInspectionTestGenerated extends AbstractK2LocalInsp runTest("../../../idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/invalidArgument.kt"); } + @TestMetadata("javaGet.kt") + public void testJavaGet() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaGet.kt"); + } + + @TestMetadata("javaSet.kt") + public void testJavaSet() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaSet.kt"); + } + + @TestMetadata("javaSet2.kt") + public void testJavaSet2() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaSet2.kt"); + } + @TestMetadata("missingDefaultArgument.kt") public void testMissingDefaultArgument() throws Exception { runTest("../../../idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/missingDefaultArgument.kt"); @@ -525,6 +540,11 @@ public abstract class K2LocalInspectionTestGenerated extends AbstractK2LocalInsp runTest("../../../idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/notOperator.kt"); } + @TestMetadata("overriddenSet.kt") + public void testOverriddenSet() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/overriddenSet.kt"); + } + @TestMetadata("qualifier.kt") public void testQualifier() throws Exception { runTest("../../../idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/qualifier.kt"); diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index c2dd00bf5740..26ccf3886f43 100644 --- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -3783,6 +3783,21 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti runTest("testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/invalidArgument.kt"); } + @TestMetadata("javaGet.kt") + public void testJavaGet() throws Exception { + runTest("testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaGet.kt"); + } + + @TestMetadata("javaSet.kt") + public void testJavaSet() throws Exception { + runTest("testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaSet.kt"); + } + + @TestMetadata("javaSet2.kt") + public void testJavaSet2() throws Exception { + runTest("testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaSet2.kt"); + } + @TestMetadata("missingDefaultArgument.kt") public void testMissingDefaultArgument() throws Exception { runTest("testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/missingDefaultArgument.kt"); @@ -3803,6 +3818,11 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti runTest("testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/notOperator.kt"); } + @TestMetadata("overriddenSet.kt") + public void testOverriddenSet() throws Exception { + runTest("testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/overriddenSet.kt"); + } + @TestMetadata("qualifier.kt") public void testQualifier() throws Exception { runTest("testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/qualifier.kt"); diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/inspectionData/expected.xml b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/inspectionData/expected.xml index eab0d0ee3a4c..5bc103b2a994 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/inspectionData/expected.xml +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/inspectionData/expected.xml @@ -64,7 +64,7 @@ set.kt - 8 + 9 light_idea_test_case Explicit 'get' or 'set' call @@ -79,4 +79,13 @@ Explicit 'get' or 'set' call Should be replaced with indexing + + + overriddenSet.kt + 19 + light_idea_test_case + + Explicit 'get' or 'set' call + Should be replaced with indexing + diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaGet.1.java b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaGet.1.java new file mode 100644 index 000000000000..764597638a7b --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaGet.1.java @@ -0,0 +1,15 @@ +public class Foo { + private int value; + + public final void set(int newValue) { + value = newValue; + } + + public final void set(int newValue, int oldValue) { + value = newValue; + } + + public final String get(String s) { + return s; + } +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaGet.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaGet.kt new file mode 100644 index 000000000000..3fadbc681598 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaGet.kt @@ -0,0 +1,4 @@ +// HIGHLIGHT: INFORMATION +fun test(foo: Foo) { + foo.get("") +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaGet.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaGet.kt.after new file mode 100644 index 000000000000..d3d9ce5ccf5a --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaGet.kt.after @@ -0,0 +1,4 @@ +// HIGHLIGHT: INFORMATION +fun test(foo: Foo) { + foo[""] +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaSet.1.java b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaSet.1.java new file mode 100644 index 000000000000..764597638a7b --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaSet.1.java @@ -0,0 +1,15 @@ +public class Foo { + private int value; + + public final void set(int newValue) { + value = newValue; + } + + public final void set(int newValue, int oldValue) { + value = newValue; + } + + public final String get(String s) { + return s; + } +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaSet.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaSet.kt new file mode 100644 index 000000000000..ac164a8afc31 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaSet.kt @@ -0,0 +1,4 @@ +// PROBLEM: none +fun test(foo: Foo) { + foo.set(Int.MAX_VALUE) +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaSet2.1.java b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaSet2.1.java new file mode 100644 index 000000000000..764597638a7b --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaSet2.1.java @@ -0,0 +1,15 @@ +public class Foo { + private int value; + + public final void set(int newValue) { + value = newValue; + } + + public final void set(int newValue, int oldValue) { + value = newValue; + } + + public final String get(String s) { + return s; + } +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaSet2.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaSet2.kt new file mode 100644 index 000000000000..419a7c5d5b01 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaSet2.kt @@ -0,0 +1,4 @@ +// HIGHLIGHT: INFORMATION +fun test(foo: Foo) { + foo.set(Int.MAX_VALUE, Int.MAX_VALUE) +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaSet2.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaSet2.kt.after new file mode 100644 index 000000000000..d9671e62ef21 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/javaSet2.kt.after @@ -0,0 +1,4 @@ +// HIGHLIGHT: INFORMATION +fun test(foo: Foo) { + foo[Int.MAX_VALUE] = Int.MAX_VALUE +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/overriddenSet.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/overriddenSet.kt new file mode 100644 index 000000000000..c5cf6bbb2119 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/overriddenSet.kt @@ -0,0 +1,20 @@ +// HIGHLIGHT: GENERIC_ERROR_OR_WARNING +open class A { + open operator fun set(s: String, value: Int) {} +} + +open class B : A() { + override fun set(s: String, value: Int) { + super.set(s, value) + } +} + +class C : B() { + override fun set(s: String, value: Int) { + super.set(s, value) + } +} + +fun foo() { + C().set("x", 1) +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/overriddenSet.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/overriddenSet.kt.after new file mode 100644 index 000000000000..b89f509da725 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/overriddenSet.kt.after @@ -0,0 +1,20 @@ +// HIGHLIGHT: GENERIC_ERROR_OR_WARNING +open class A { + open operator fun set(s: String, value: Int) {} +} + +open class B : A() { + override fun set(s: String, value: Int) { + super.set(s, value) + } +} + +class C : B() { + override fun set(s: String, value: Int) { + super.set(s, value) + } +} + +fun foo() { + C()["x"] = 1 +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/set.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/set.kt index bde3716e1e9f..2e339ca33948 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/set.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/set.kt @@ -1,3 +1,4 @@ +// HIGHLIGHT: GENERIC_ERROR_OR_WARNING // FIX: Replace 'set' call with indexing operator class C { diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/set.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/set.kt.after index f72a63af23b9..8193062c41d4 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/set.kt.after +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/set.kt.after @@ -1,3 +1,4 @@ +// HIGHLIGHT: GENERIC_ERROR_OR_WARNING // FIX: Replace 'set' call with indexing operator class C {