From bf7f56d458f6f89d6def5c68579ad6f256eb4dc2 Mon Sep 17 00:00:00 2001 From: Natalia Ukhorskaya Date: Thu, 13 Aug 2015 14:16:37 +0300 Subject: [PATCH] Redundant Sam Constructor inspection --- .../java/sam/SingleAbstractMethodUtils.java | 2 +- idea/src/META-INF/plugin.xml | 7 + .../RedundantSamConstructorInspection.kt | 223 ++++++++++++++++++ .../redundantSamConstructor/a/A.java | 9 + .../a/GenericClass1.java | 11 + .../a/GenericClass2.java | 11 + .../a/GenericClassCantBeReplaced.java | 15 ++ .../redundantSamConstructor/a/JFunction0.java | 5 + .../redundantSamConstructor/a/JFunction1.java | 5 + .../a/MyJavaClass.java | 14 ++ .../redundantSamConstructor/expectedType.kt | 9 + .../inspectionData/expected.xml | 138 +++++++++++ .../inspectionData/expectedType.kt.after | 9 + .../inspectionData/inspections.test | 1 + .../otherGenericsInParams.kt.after | 12 + .../redundantSamConstructor.kt.after | 40 ++++ ...dundantSamConstructorWithGenerics.kt.after | 26 ++ ...nstructorWithGenericsInReturnType.kt.after | 26 ++ .../redundantSamConstructor/nonApplicable.kt | 12 + .../otherGenericsInParams.kt | 12 + .../redundantSamConstructor.kt | 40 ++++ .../redundantSamConstructorWithGenerics.kt | 26 ++ ...tSamConstructorWithGenericsInReturnType.kt | 26 ++ .../codeInsight/AbstractJetInspectionTest.kt | 22 ++ .../JetInspectionTestGenerated.java | 6 + 25 files changed, 706 insertions(+), 1 deletion(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/inspections/RedundantSamConstructorInspection.kt create mode 100644 idea/testData/inspections/redundantSamConstructor/a/A.java create mode 100644 idea/testData/inspections/redundantSamConstructor/a/GenericClass1.java create mode 100644 idea/testData/inspections/redundantSamConstructor/a/GenericClass2.java create mode 100644 idea/testData/inspections/redundantSamConstructor/a/GenericClassCantBeReplaced.java create mode 100644 idea/testData/inspections/redundantSamConstructor/a/JFunction0.java create mode 100644 idea/testData/inspections/redundantSamConstructor/a/JFunction1.java create mode 100644 idea/testData/inspections/redundantSamConstructor/a/MyJavaClass.java create mode 100644 idea/testData/inspections/redundantSamConstructor/expectedType.kt create mode 100644 idea/testData/inspections/redundantSamConstructor/inspectionData/expected.xml create mode 100644 idea/testData/inspections/redundantSamConstructor/inspectionData/expectedType.kt.after create mode 100644 idea/testData/inspections/redundantSamConstructor/inspectionData/inspections.test create mode 100644 idea/testData/inspections/redundantSamConstructor/inspectionData/otherGenericsInParams.kt.after create mode 100644 idea/testData/inspections/redundantSamConstructor/inspectionData/redundantSamConstructor.kt.after create mode 100644 idea/testData/inspections/redundantSamConstructor/inspectionData/redundantSamConstructorWithGenerics.kt.after create mode 100644 idea/testData/inspections/redundantSamConstructor/inspectionData/redundantSamConstructorWithGenericsInReturnType.kt.after create mode 100644 idea/testData/inspections/redundantSamConstructor/nonApplicable.kt create mode 100644 idea/testData/inspections/redundantSamConstructor/otherGenericsInParams.kt create mode 100644 idea/testData/inspections/redundantSamConstructor/redundantSamConstructor.kt create mode 100644 idea/testData/inspections/redundantSamConstructor/redundantSamConstructorWithGenerics.kt create mode 100644 idea/testData/inspections/redundantSamConstructor/redundantSamConstructorWithGenericsInReturnType.kt diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/sam/SingleAbstractMethodUtils.java b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/sam/SingleAbstractMethodUtils.java index 0f90b74fefa..09f39989947 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/sam/SingleAbstractMethodUtils.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/sam/SingleAbstractMethodUtils.java @@ -86,7 +86,7 @@ public class SingleAbstractMethodUtils { } @Nullable - private static JetType getFunctionTypeForSamType(@NotNull JetType samType) { + public static JetType getFunctionTypeForSamType(@NotNull JetType samType) { // e.g. samType == Comparator? ClassifierDescriptor classifier = samType.getConstructor().getDeclarationDescriptor(); diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 36f288df435..c5f61b84b3d 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -1078,6 +1078,13 @@ level="WARNING" /> + + ): LocalQuickFix { + return object : LocalQuickFix { + override fun getName() = "Remove redundant SAM-constructors" + override fun getFamilyName() = name + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + for (callExpression in expressions) { + replaceSamConstructorCall(callExpression) + } + } + } + } + + override fun visitCallExpression(expression: JetCallExpression) { + if (expression.valueArguments.isEmpty()) return + + val samConstructorCalls = samConstructorCallsToBeConverted(expression) + if (samConstructorCalls.isEmpty()) return + if (samConstructorCalls.size() == 1) { + val single = samConstructorCalls.single() + val problemDescriptor = holder.manager. + createProblemDescriptor(single.calleeExpression!!, + single.typeArgumentList ?: single.calleeExpression!!, + "Redundant SAM-constructor", + ProblemHighlightType.LIKE_UNUSED_SYMBOL, + isOnTheFly, + createQuickFix(single)) + + holder.registerProblem(problemDescriptor) + } + else { + val problemDescriptor = holder.manager. + createProblemDescriptor(expression.valueArgumentList!!, + "Redundant SAM-constructors", + createQuickFix(samConstructorCalls), + ProblemHighlightType.WEAK_WARNING, + isOnTheFly) + + holder.registerProblem(problemDescriptor) + } + } + } + } + + companion object { + public fun replaceSamConstructorCall(callExpression: JetCallExpression): JetExpression { + val functionalArgument = callExpression.samConstructorValueArgument()?.getArgumentExpression() + ?: throw AssertionError("SAM-constructor should have a FunctionLiteralExpression as single argument: ${callExpression.getElementTextWithContext()}") + return callExpression.replace(functionalArgument) as JetExpression + } + + private fun canBeReplaced(parentCall: JetCallExpression, callExpressions: List): Boolean { + val context = parentCall.analyze(BodyResolveMode.PARTIAL) + + val calleeExpression = parentCall.calleeExpression ?: return false + val scope = context[BindingContext.RESOLUTION_SCOPE, calleeExpression] ?: return false + + val originalCall = parentCall.getResolvedCall(context) ?: return false + + val dataFlow = context.getDataFlowInfo(parentCall) + val callResolver = parentCall.getResolutionFacade().frontendService() + val newCall = CallWithNewArguments(originalCall.call, callExpressions) + + val qualifiedExpression = parentCall.getQualifiedExpressionForSelectorOrThis() + val expectedType = context[BindingContext.EXPECTED_EXPRESSION_TYPE, qualifiedExpression] ?: TypeUtils.NO_EXPECTED_TYPE + + val resolutionResults = callResolver.resolveFunctionCall(BindingTraceContext(), scope, newCall, expectedType, dataFlow, false) + + if (!resolutionResults.isSuccess) return false + + val samAdapterOriginalDescriptor = SamCodegenUtil.getOriginalIfSamAdapter(resolutionResults.resultingDescriptor) ?: return false + return samAdapterOriginalDescriptor.original == originalCall.resultingDescriptor.original + } + + private class CallWithNewArguments(original: Call, val callExpressions: List): DelegatingCall(original) { + private val newArguments: List + + init { + val factory = JetPsiFactory(callElement) + newArguments = original.valueArguments.map { argument -> + argument.getArgumentExpression() + ?.check { callExpressions.contains(it) } + ?.let { factory.createArgument(replaceSamConstructorCall(it.copy() as JetCallExpression), argument.getArgumentName()?.asName) } + ?: argument + } + } + + override fun getValueArguments() = newArguments + } + + public fun samConstructorCallsToBeConverted(functionCall: JetCallExpression): List { + if (functionCall.valueArguments.all { !canBeSamConstructorCall(it) }) { + return emptyList() + } + + val bindingContext = functionCall.analyze(BodyResolveMode.PARTIAL) + val functionResolvedCall = functionCall.getResolvedCall(bindingContext) ?: return emptyList() + if (!functionResolvedCall.status.isSuccess) return emptyList() + + val samConstructorCalls = functionCall.valueArguments.map { + (it.getArgumentExpression() as? JetCallExpression) + ?.check { it.getResolvedCall(bindingContext)?.resultingDescriptor?.original is SamConstructorDescriptor } + }.filterNotNull() + + if (samConstructorCalls.isEmpty()) return emptyList() + + val originalFunctionDescriptor = functionResolvedCall.resultingDescriptor.original as? FunctionDescriptor ?: return emptyList() + val containingClass = originalFunctionDescriptor.containingDeclaration as? ClassDescriptor ?: return emptyList() + + // SAM adapters for static functions + for (staticFunWithSameName in containingClass.staticScope.getFunctions(functionResolvedCall.resultingDescriptor.name, NoLookupLocation.FROM_IDE)) { + if (staticFunWithSameName is SamAdapterDescriptor<*>) { + if (isSamAdapterSuitableForCall(staticFunWithSameName, originalFunctionDescriptor, samConstructorCalls.size())) { + return samConstructorCalls.check { canBeReplaced(functionCall, it) } ?: emptyList() + } + } + } + + // SAM adapters for member functions + val resolutionScope = functionCall.getResolutionScope(bindingContext, functionCall.getResolutionFacade()) + val syntheticExtensions = resolutionScope.getSyntheticExtensionFunctions( + containingClass.defaultType.singletonList(), + functionResolvedCall.resultingDescriptor.name, + NoLookupLocation.FROM_IDE + ) + for (syntheticExtension in syntheticExtensions) { + val samAdapter = syntheticExtension as? SamAdapterExtensionFunctionDescriptor ?: continue + if (isSamAdapterSuitableForCall(samAdapter, originalFunctionDescriptor, samConstructorCalls.size())) { + return samConstructorCalls.check { canBeReplaced(functionCall, it) } ?: emptyList() + } + } + + return emptyList() + } + + private fun canBeSamConstructorCall(argument: JetValueArgument): Boolean { + val argumentExpression = argument.getArgumentExpression() + if (argumentExpression !is JetCallExpression) return false + + return argumentExpression.samConstructorValueArgument() != null + } + + private fun JetCallExpression.samConstructorValueArgument(): JetValueArgument? { + return valueArguments.singleOrNull()?.check { it.getArgumentExpression() is JetFunctionLiteralExpression } + } + + private fun isSamAdapterSuitableForCall( + samAdapter: FunctionDescriptor, + originalFunction: FunctionDescriptor, + samConstructorsCount: Int + ): Boolean { + val samAdapterOriginalFunction = SamCodegenUtil.getOriginalIfSamAdapter(samAdapter)?.original + if (samAdapterOriginalFunction != originalFunction) return false + + val parametersWithSamTypeCount = originalFunction.valueParameters.count { + SingleAbstractMethodUtils.getFunctionTypeForSamType(it.type) != null + } + + return parametersWithSamTypeCount == samConstructorsCount + } + } +} \ No newline at end of file diff --git a/idea/testData/inspections/redundantSamConstructor/a/A.java b/idea/testData/inspections/redundantSamConstructor/a/A.java new file mode 100644 index 00000000000..9e2d3d70bf2 --- /dev/null +++ b/idea/testData/inspections/redundantSamConstructor/a/A.java @@ -0,0 +1,9 @@ +package a; + +public class A { + public void foo(JFunction1 r) {} + + public static JFunction1 expectedType(JFunction1 r) { + return r; + } +} \ No newline at end of file diff --git a/idea/testData/inspections/redundantSamConstructor/a/GenericClass1.java b/idea/testData/inspections/redundantSamConstructor/a/GenericClass1.java new file mode 100644 index 00000000000..d2bb0effd3e --- /dev/null +++ b/idea/testData/inspections/redundantSamConstructor/a/GenericClass1.java @@ -0,0 +1,11 @@ +package a; + +public class GenericClass1 { + public static void staticFun1(JFunction1 r) {} + public static void staticFun2(JFunction1 r1, JFunction1 r2) {} + public static void staticFunWithOtherParam(int r1, JFunction1 r2) {} + + public void memberFun1(JFunction1 r) {} + public void memberFun2(JFunction1 r1, JFunction1 r2) {} + public void memberFunWithOtherParam(int r1, JFunction1 r2) {} +} \ No newline at end of file diff --git a/idea/testData/inspections/redundantSamConstructor/a/GenericClass2.java b/idea/testData/inspections/redundantSamConstructor/a/GenericClass2.java new file mode 100644 index 00000000000..443016d8ad6 --- /dev/null +++ b/idea/testData/inspections/redundantSamConstructor/a/GenericClass2.java @@ -0,0 +1,11 @@ +package a; + +public class GenericClass2 { + public static void staticFun1(JFunction0 r) {} + public static void staticFun2(JFunction0 r1, JFunction0 r2) {} + public static void staticFunWithOtherParam(int r1, JFunction0 r2) {} + + public void memberFun1(JFunction0 r) {} + public void memberFun2(JFunction0 r1, JFunction0 r2) {} + public void memberFunWithOtherParam(int r1, JFunction0 r2) {} +} \ No newline at end of file diff --git a/idea/testData/inspections/redundantSamConstructor/a/GenericClassCantBeReplaced.java b/idea/testData/inspections/redundantSamConstructor/a/GenericClassCantBeReplaced.java new file mode 100644 index 00000000000..a8efda951b6 --- /dev/null +++ b/idea/testData/inspections/redundantSamConstructor/a/GenericClassCantBeReplaced.java @@ -0,0 +1,15 @@ +package a; + +public class GenericClassCantBeReplaced { + public static void staticFun1(JFunction0 r) {} + public static void staticFun1(JFunction1 r) {} + + public static void staticFun2(JFunction0 r1, JFunction0 r2) {} + public static void staticFun2(JFunction1 r1, JFunction1 r2) {} + + public void memberFun1(JFunction0 r) {} + public void memberFun1(JFunction1 r) {} + + public void memberFun2(JFunction0 r1, JFunction0 r2) {} + public void memberFun2(JFunction1 r1, JFunction1 r2) {} +} \ No newline at end of file diff --git a/idea/testData/inspections/redundantSamConstructor/a/JFunction0.java b/idea/testData/inspections/redundantSamConstructor/a/JFunction0.java new file mode 100644 index 00000000000..b59e4420cfc --- /dev/null +++ b/idea/testData/inspections/redundantSamConstructor/a/JFunction0.java @@ -0,0 +1,5 @@ +package a; + +public interface JFunction0 { + T foo(); +} \ No newline at end of file diff --git a/idea/testData/inspections/redundantSamConstructor/a/JFunction1.java b/idea/testData/inspections/redundantSamConstructor/a/JFunction1.java new file mode 100644 index 00000000000..c347802c816 --- /dev/null +++ b/idea/testData/inspections/redundantSamConstructor/a/JFunction1.java @@ -0,0 +1,5 @@ +package a; + +public interface JFunction1 { + void foo(T t); +} \ No newline at end of file diff --git a/idea/testData/inspections/redundantSamConstructor/a/MyJavaClass.java b/idea/testData/inspections/redundantSamConstructor/a/MyJavaClass.java new file mode 100644 index 00000000000..3400190de85 --- /dev/null +++ b/idea/testData/inspections/redundantSamConstructor/a/MyJavaClass.java @@ -0,0 +1,14 @@ +package a; + +public class MyJavaClass { + public static void staticFun1(Runnable r) {} + public static void staticFun2(Runnable r1, Runnable r2) {} + public static void staticFunWithOtherParam(int r1, Runnable r2) {} + + public void memberFun1(Runnable r) {} + public void memberFun2(Runnable r1, Runnable r2) {} + public void memberFunWithOtherParam(int r1, Runnable r2) {} + + public static void foo1(Runnable r, T t) {} + public static void foo2(T t, Runnable r) {} +} \ No newline at end of file diff --git a/idea/testData/inspections/redundantSamConstructor/expectedType.kt b/idea/testData/inspections/redundantSamConstructor/expectedType.kt new file mode 100644 index 00000000000..881a9af5793 --- /dev/null +++ b/idea/testData/inspections/redundantSamConstructor/expectedType.kt @@ -0,0 +1,9 @@ +package redundantSamConstructor + +import a.* +import a.A.* + +fun testExpectedType() { + val s1: JFunction1 = A.expectedType(JFunction1 { }) + val s2: JFunction1 = expectedType(JFunction1 { }) +} \ No newline at end of file diff --git a/idea/testData/inspections/redundantSamConstructor/inspectionData/expected.xml b/idea/testData/inspections/redundantSamConstructor/inspectionData/expected.xml new file mode 100644 index 00000000000..addc212bdfe --- /dev/null +++ b/idea/testData/inspections/redundantSamConstructor/inspectionData/expected.xml @@ -0,0 +1,138 @@ + + + redundantSamConstructor.kt + 9 + light_idea_test_case + + Redundant SAM-constructor + Redundant SAM-constructor + + + redundantSamConstructor.kt + 11 + light_idea_test_case + + Redundant SAM-constructors + Redundant SAM-constructor + + + redundantSamConstructor.kt + 22 + light_idea_test_case + + Redundant SAM-constructor + Redundant SAM-constructor + + + redundantSamConstructor.kt + 25 + light_idea_test_case + + Redundant SAM-constructor + Redundant SAM-constructor + + + redundantSamConstructor.kt + 27 + light_idea_test_case + + Redundant SAM-constructor + Redundant SAM-constructor + + + redundantSamConstructor.kt + 38 + light_idea_test_case + + Redundant SAM-constructor + Redundant SAM-constructor + + + redundantSamConstructorWithGenericsInReturnType.kt + 9 + light_idea_test_case + + Redundant SAM-constructor + Redundant SAM-constructor + + + redundantSamConstructorWithGenericsInReturnType.kt + 11 + light_idea_test_case + + Redundant SAM-constructors + Redundant SAM-constructor + + + redundantSamConstructorWithGenericsInReturnType.kt + 15 + light_idea_test_case + + Redundant SAM-constructor + Redundant SAM-constructor + + + redundantSamConstructorWithGenericsInReturnType.kt + 18 + light_idea_test_case + + Redundant SAM-constructor + Redundant SAM-constructor + + + redundantSamConstructorWithGenericsInReturnType.kt + 20 + light_idea_test_case + + Redundant SAM-constructor + Redundant SAM-constructor + + + redundantSamConstructorWithGenericsInReturnType.kt + 24 + light_idea_test_case + + Redundant SAM-constructor + Redundant SAM-constructor + + + otherGenericsInParams.kt + 6 + light_idea_test_case + + Redundant SAM-constructor + Redundant SAM-constructor + + + otherGenericsInParams.kt + 8 + light_idea_test_case + + Redundant SAM-constructor + Redundant SAM-constructor + + + otherGenericsInParams.kt + 11 + light_idea_test_case + + Redundant SAM-constructor + Redundant SAM-constructor + + + expectedType.kt + 7 + light_idea_test_case + + Redundant SAM constructor + Redundant SAM-constructor + + + expectedType.kt + 8 + light_idea_test_case + + Redundant SAM constructor + Redundant SAM-constructor + + \ No newline at end of file diff --git a/idea/testData/inspections/redundantSamConstructor/inspectionData/expectedType.kt.after b/idea/testData/inspections/redundantSamConstructor/inspectionData/expectedType.kt.after new file mode 100644 index 00000000000..527e47d8ab4 --- /dev/null +++ b/idea/testData/inspections/redundantSamConstructor/inspectionData/expectedType.kt.after @@ -0,0 +1,9 @@ +package redundantSamConstructor + +import a.* +import a.A.* + +fun testExpectedType() { + val s1: JFunction1 = A.expectedType({ }) + val s2: JFunction1 = expectedType({ }) +} \ No newline at end of file diff --git a/idea/testData/inspections/redundantSamConstructor/inspectionData/inspections.test b/idea/testData/inspections/redundantSamConstructor/inspectionData/inspections.test new file mode 100644 index 00000000000..d58e058f297 --- /dev/null +++ b/idea/testData/inspections/redundantSamConstructor/inspectionData/inspections.test @@ -0,0 +1 @@ +// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.RedundantSamConstructorInspection diff --git a/idea/testData/inspections/redundantSamConstructor/inspectionData/otherGenericsInParams.kt.after b/idea/testData/inspections/redundantSamConstructor/inspectionData/otherGenericsInParams.kt.after new file mode 100644 index 00000000000..bff3e5fbb76 --- /dev/null +++ b/idea/testData/inspections/redundantSamConstructor/inspectionData/otherGenericsInParams.kt.after @@ -0,0 +1,12 @@ +package redundantSamConstructor + +import a.* + +fun test() { + MyJavaClass.foo1({ }, 1) + MyJavaClass.foo1(Runnable { }, Runnable { }) + MyJavaClass.foo2(1, { }) + MyJavaClass.foo2(Runnable { }, Runnable { }) + + A().foo({}) +} \ No newline at end of file diff --git a/idea/testData/inspections/redundantSamConstructor/inspectionData/redundantSamConstructor.kt.after b/idea/testData/inspections/redundantSamConstructor/inspectionData/redundantSamConstructor.kt.after new file mode 100644 index 00000000000..b01cf1d2b93 --- /dev/null +++ b/idea/testData/inspections/redundantSamConstructor/inspectionData/redundantSamConstructor.kt.after @@ -0,0 +1,40 @@ +package redundantSamConstructor + +import a.* + +fun test() { + val runnable = Runnable { } + val klass = MyJavaClass() + + MyJavaClass.staticFun1({ }) + MyJavaClass.staticFun1(runnable) + MyJavaClass.staticFun2({ }, { }) + MyJavaClass.staticFun2(runnable, Runnable { }) + MyJavaClass.staticFun2({ }, { }) + MyJavaClass.staticFun2( + object: Runnable { + override fun run() { } + }, + object: Runnable { + override fun run() { } + }) + + MyJavaClass.staticFunWithOtherParam(1, { }) + MyJavaClass.staticFunWithOtherParam(1, runnable) + + klass.memberFun1({ }) + klass.memberFun1(runnable) + klass.memberFun2({ }, { }) + klass.memberFun2(runnable, Runnable { }) + klass.memberFun2({ }, { }) + klass.memberFun2( + object: Runnable { + override fun run() { } + }, + object: Runnable { + override fun run() { } + }) + + klass.memberFunWithOtherParam(1, { }) + klass.memberFunWithOtherParam(1, runnable) +} \ No newline at end of file diff --git a/idea/testData/inspections/redundantSamConstructor/inspectionData/redundantSamConstructorWithGenerics.kt.after b/idea/testData/inspections/redundantSamConstructor/inspectionData/redundantSamConstructorWithGenerics.kt.after new file mode 100644 index 00000000000..e0bea2e7cad --- /dev/null +++ b/idea/testData/inspections/redundantSamConstructor/inspectionData/redundantSamConstructorWithGenerics.kt.after @@ -0,0 +1,26 @@ +package redundantSamConstructor + +import a.* + +fun testGenerics() { + val runnable = JFunction1 { } + val klass = GenericClass1() + + GenericClass1.staticFun1(JFunction1 { }) + GenericClass1.staticFun1(runnable) + GenericClass1.staticFun2(JFunction1 { }, JFunction1 { }) + GenericClass1.staticFun2(runnable, JFunction1 { }) + GenericClass1.staticFun2({ s: String -> }, { s: String -> }) + + GenericClass1.staticFunWithOtherParam(1, JFunction1 { }) + GenericClass1.staticFunWithOtherParam(1, runnable) + + klass.memberFun1(JFunction1 { }) + klass.memberFun1(runnable) + klass.memberFun2(JFunction1 { }, JFunction1 { }) + klass.memberFun2(runnable, JFunction1 { }) + klass.memberFun2({ s: String -> }, { s: String -> }) + + klass.memberFunWithOtherParam(1, JFunction1 { }) + klass.memberFunWithOtherParam(1, runnable) +} \ No newline at end of file diff --git a/idea/testData/inspections/redundantSamConstructor/inspectionData/redundantSamConstructorWithGenericsInReturnType.kt.after b/idea/testData/inspections/redundantSamConstructor/inspectionData/redundantSamConstructorWithGenericsInReturnType.kt.after new file mode 100644 index 00000000000..9b66b7ece1b --- /dev/null +++ b/idea/testData/inspections/redundantSamConstructor/inspectionData/redundantSamConstructorWithGenericsInReturnType.kt.after @@ -0,0 +1,26 @@ +package redundantSamConstructor + +import a.* + +fun testGenericsReturnType() { + val runnable = JFunction0 { "" } + val klass = GenericClass2() + + GenericClass2.staticFun1({ "" }) + GenericClass2.staticFun1(runnable) + GenericClass2.staticFun2({ "" }, { "" }) + GenericClass2.staticFun2(runnable, JFunction0 { "" }) + GenericClass2.staticFun2({ -> "" }, { -> "" }) + + GenericClass2.staticFunWithOtherParam(1, { "" }) + GenericClass2.staticFunWithOtherParam(1, runnable) + + klass.memberFun1({ "" }) + klass.memberFun1(runnable) + klass.memberFun2({ "" }, { "" }) + klass.memberFun2(runnable, JFunction0 { "" }) + klass.memberFun2({ -> "" }, { -> "" }) + + klass.memberFunWithOtherParam(1, { "" }) + klass.memberFunWithOtherParam(1, runnable) +} \ No newline at end of file diff --git a/idea/testData/inspections/redundantSamConstructor/nonApplicable.kt b/idea/testData/inspections/redundantSamConstructor/nonApplicable.kt new file mode 100644 index 00000000000..58262ad8af0 --- /dev/null +++ b/idea/testData/inspections/redundantSamConstructor/nonApplicable.kt @@ -0,0 +1,12 @@ +package redundantSamConstructor + +import a.* + +fun testNonApplicableAmbiguity() { + GenericClassCantBeReplaced.staticFun1(JFunction0 { "" }) + GenericClassCantBeReplaced.staticFun2(JFunction0 { "" }, JFunction0 { "" }) + + val klass = GenericClassCantBeReplaced() + klass.memberFun1(JFunction0 { "" }) + klass.memberFun2(JFunction0 { "" }, JFunction0 { "" }) +} \ No newline at end of file diff --git a/idea/testData/inspections/redundantSamConstructor/otherGenericsInParams.kt b/idea/testData/inspections/redundantSamConstructor/otherGenericsInParams.kt new file mode 100644 index 00000000000..e9f8adbf8d0 --- /dev/null +++ b/idea/testData/inspections/redundantSamConstructor/otherGenericsInParams.kt @@ -0,0 +1,12 @@ +package redundantSamConstructor + +import a.* + +fun test() { + MyJavaClass.foo1(Runnable { }, 1) + MyJavaClass.foo1(Runnable { }, Runnable { }) + MyJavaClass.foo2(1, Runnable { }) + MyJavaClass.foo2(Runnable { }, Runnable { }) + + A().foo(JFunction1 {}) +} \ No newline at end of file diff --git a/idea/testData/inspections/redundantSamConstructor/redundantSamConstructor.kt b/idea/testData/inspections/redundantSamConstructor/redundantSamConstructor.kt new file mode 100644 index 00000000000..e5be4404942 --- /dev/null +++ b/idea/testData/inspections/redundantSamConstructor/redundantSamConstructor.kt @@ -0,0 +1,40 @@ +package redundantSamConstructor + +import a.* + +fun test() { + val runnable = Runnable { } + val klass = MyJavaClass() + + MyJavaClass.staticFun1(Runnable { }) + MyJavaClass.staticFun1(runnable) + MyJavaClass.staticFun2(Runnable { }, Runnable { }) + MyJavaClass.staticFun2(runnable, Runnable { }) + MyJavaClass.staticFun2({ }, { }) + MyJavaClass.staticFun2( + object: Runnable { + override fun run() { } + }, + object: Runnable { + override fun run() { } + }) + + MyJavaClass.staticFunWithOtherParam(1, Runnable { }) + MyJavaClass.staticFunWithOtherParam(1, runnable) + + klass.memberFun1(Runnable { }) + klass.memberFun1(runnable) + klass.memberFun2(Runnable { }, Runnable { }) + klass.memberFun2(runnable, Runnable { }) + klass.memberFun2({ }, { }) + klass.memberFun2( + object: Runnable { + override fun run() { } + }, + object: Runnable { + override fun run() { } + }) + + klass.memberFunWithOtherParam(1, Runnable { }) + klass.memberFunWithOtherParam(1, runnable) +} \ No newline at end of file diff --git a/idea/testData/inspections/redundantSamConstructor/redundantSamConstructorWithGenerics.kt b/idea/testData/inspections/redundantSamConstructor/redundantSamConstructorWithGenerics.kt new file mode 100644 index 00000000000..e0bea2e7cad --- /dev/null +++ b/idea/testData/inspections/redundantSamConstructor/redundantSamConstructorWithGenerics.kt @@ -0,0 +1,26 @@ +package redundantSamConstructor + +import a.* + +fun testGenerics() { + val runnable = JFunction1 { } + val klass = GenericClass1() + + GenericClass1.staticFun1(JFunction1 { }) + GenericClass1.staticFun1(runnable) + GenericClass1.staticFun2(JFunction1 { }, JFunction1 { }) + GenericClass1.staticFun2(runnable, JFunction1 { }) + GenericClass1.staticFun2({ s: String -> }, { s: String -> }) + + GenericClass1.staticFunWithOtherParam(1, JFunction1 { }) + GenericClass1.staticFunWithOtherParam(1, runnable) + + klass.memberFun1(JFunction1 { }) + klass.memberFun1(runnable) + klass.memberFun2(JFunction1 { }, JFunction1 { }) + klass.memberFun2(runnable, JFunction1 { }) + klass.memberFun2({ s: String -> }, { s: String -> }) + + klass.memberFunWithOtherParam(1, JFunction1 { }) + klass.memberFunWithOtherParam(1, runnable) +} \ No newline at end of file diff --git a/idea/testData/inspections/redundantSamConstructor/redundantSamConstructorWithGenericsInReturnType.kt b/idea/testData/inspections/redundantSamConstructor/redundantSamConstructorWithGenericsInReturnType.kt new file mode 100644 index 00000000000..7ac0d5b25b6 --- /dev/null +++ b/idea/testData/inspections/redundantSamConstructor/redundantSamConstructorWithGenericsInReturnType.kt @@ -0,0 +1,26 @@ +package redundantSamConstructor + +import a.* + +fun testGenericsReturnType() { + val runnable = JFunction0 { "" } + val klass = GenericClass2() + + GenericClass2.staticFun1(JFunction0 { "" }) + GenericClass2.staticFun1(runnable) + GenericClass2.staticFun2(JFunction0 { "" }, JFunction0 { "" }) + GenericClass2.staticFun2(runnable, JFunction0 { "" }) + GenericClass2.staticFun2({ -> "" }, { -> "" }) + + GenericClass2.staticFunWithOtherParam(1, JFunction0 { "" }) + GenericClass2.staticFunWithOtherParam(1, runnable) + + klass.memberFun1(JFunction0 { "" }) + klass.memberFun1(runnable) + klass.memberFun2(JFunction0 { "" }, JFunction0 { "" }) + klass.memberFun2(runnable, JFunction0 { "" }) + klass.memberFun2({ -> "" }, { -> "" }) + + klass.memberFunWithOtherParam(1, JFunction0 { "" }) + klass.memberFunWithOtherParam(1, runnable) +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractJetInspectionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractJetInspectionTest.kt index b42cfda6f99..4745c7f07b9 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractJetInspectionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractJetInspectionTest.kt @@ -22,7 +22,9 @@ import com.intellij.codeInspection.LocalInspectionTool import com.intellij.codeInspection.ex.EntryPointsManagerBase import com.intellij.codeInspection.ex.InspectionManagerEx import com.intellij.codeInspection.ex.LocalInspectionToolWrapper +import com.intellij.openapi.command.CommandProcessor import com.intellij.openapi.util.io.FileUtil +import com.intellij.psi.PsiFile import com.intellij.testFramework.IdeaTestUtil import com.intellij.testFramework.InspectionTestUtil import com.intellij.testFramework.LightProjectDescriptor @@ -31,10 +33,12 @@ import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil import org.jetbrains.kotlin.idea.test.JetLightCodeInsightFixtureTestCase import org.jetbrains.kotlin.idea.test.JetLightProjectDescriptor import org.jetbrains.kotlin.idea.test.PluginTestCaseBase +import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.test.InTextDirectivesUtils import org.jetbrains.kotlin.test.JetTestUtils import java.io.File import kotlin.test.assertFalse +import java.util.* public abstract class AbstractJetInspectionTest: JetLightCodeInsightFixtureTestCase() { companion object { @@ -66,6 +70,7 @@ public abstract class AbstractJetInspectionTest: JetLightCodeInsightFixtureTestC with(myFixture) { setTestDataPath("${JetTestUtils.getHomeDirectory()}/$srcDir") + val afterFiles = srcDir.listFiles { it.name == "inspectionData" }?.single()?.listFiles { it.extension == "after" } ?: emptyArray() val psiFiles = srcDir.walkTopDown().filter { it.name != "inspectionData" }.map { file -> if (file.isDirectory) { @@ -115,6 +120,23 @@ public abstract class AbstractJetInspectionTest: JetLightCodeInsightFixtureTestC InspectionTestUtil.runTool(toolWrapper, scope, globalContext) InspectionTestUtil.compareToolResults(globalContext, toolWrapper, false, inspectionsTestDir.getPath()) + + if (afterFiles.isNotEmpty()) { + globalContext.getPresentation(toolWrapper).problemDescriptors.forEach { + problem -> + problem.fixes?.forEach { + CommandProcessor.getInstance().executeCommand(project, { + runWriteAction { it.applyFix(project, problem) } + }, it.name, it.familyName) + } + } + + for (filePath in afterFiles) { + val kotlinFile = psiFiles.first { filePath.name == it.name + ".after" } + JetTestUtils.assertEqualsToFile(filePath, kotlinFile.text) + } + } + } finally { if (isWithRuntime) { diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetInspectionTestGenerated.java index 9180c1e0f53..6d4876ccbc1 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetInspectionTestGenerated.java @@ -88,6 +88,12 @@ public class JetInspectionTestGenerated extends AbstractJetInspectionTest { JetTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/inspections"), Pattern.compile("^(inspections\\.test)$")); } + @TestMetadata("redundantSamConstructor/inspectionData/inspections.test") + public void testRedundantSamConstructor_inspectionData_Inspections_test() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/inspections/redundantSamConstructor/inspectionData/inspections.test"); + doTest(fileName); + } + @TestMetadata("spelling/inspectionData/inspections.test") public void testSpelling_inspectionData_Inspections_test() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/inspections/spelling/inspectionData/inspections.test");