From b8ebc087e23f4d0e04d761e4a43e0c6a90282b7e Mon Sep 17 00:00:00 2001 From: Vyacheslav Gerasimov Date: Mon, 13 Mar 2017 19:56:59 +0300 Subject: [PATCH] Add inspection for calls of function with lambda expression body Added "Unused return value of a function with lambda expression body" inspection with quickfix "Remove '=' token from function declaration" #KT-10393 Fixed --- .../UnusedLambdaExpressionBody.html | 6 ++ idea/src/META-INF/plugin.xml | 8 ++ .../UnusedLambdaExpressionBodyInspection.kt | 87 +++++++++++++++++++ .../inspectionData/expected.xml | 18 ++++ .../inspectionData/inspections.test | 1 + .../unusedLambdaExpressionBody/simple.kt | 13 +++ .../.inspection | 1 + .../simple.kt | 9 ++ .../simple.kt.after | 9 ++ .../codeInsight/InspectionTestGenerated.java | 6 ++ .../idea/quickfix/QuickFixTestGenerated.java | 15 ++++ 11 files changed, 173 insertions(+) create mode 100644 idea/resources/inspectionDescriptions/UnusedLambdaExpressionBody.html create mode 100644 idea/src/org/jetbrains/kotlin/idea/inspections/UnusedLambdaExpressionBodyInspection.kt create mode 100644 idea/testData/inspections/unusedLambdaExpressionBody/inspectionData/expected.xml create mode 100644 idea/testData/inspections/unusedLambdaExpressionBody/inspectionData/inspections.test create mode 100644 idea/testData/inspections/unusedLambdaExpressionBody/simple.kt create mode 100644 idea/testData/quickfix/removeEqTokenFromFunctionDeclaration/.inspection create mode 100644 idea/testData/quickfix/removeEqTokenFromFunctionDeclaration/simple.kt create mode 100644 idea/testData/quickfix/removeEqTokenFromFunctionDeclaration/simple.kt.after diff --git a/idea/resources/inspectionDescriptions/UnusedLambdaExpressionBody.html b/idea/resources/inspectionDescriptions/UnusedLambdaExpressionBody.html new file mode 100644 index 00000000000..bbc8be913ff --- /dev/null +++ b/idea/resources/inspectionDescriptions/UnusedLambdaExpressionBody.html @@ -0,0 +1,6 @@ + + +This inspection reports calls with unused return value when the function is defined with lambda as expression body. +Happens when someone accidentally puts '=' between function header and body block. + + diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 387b4e8ada0..cdfac892b4c 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -2071,6 +2071,14 @@ language="kotlin" /> + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedLambdaExpressionBodyInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedLambdaExpressionBodyInspection.kt new file mode 100644 index 00000000000..52988b39ec8 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedLambdaExpressionBodyInspection.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.inspections + +import com.intellij.codeInsight.FileModificationService +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiElementVisitor +import org.jetbrains.kotlin.builtins.isFunctionType +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.resolve.source.getPsi + + +class UnusedLambdaExpressionBodyInspection : AbstractKotlinInspection() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { + return object : KtVisitorVoid() { + override fun visitCallExpression(expression: KtCallExpression) { + val context = expression.analyze(BodyResolveMode.PARTIAL) + if (expression.used(context)) { + return + } + + val descriptor = expression.getResolvedCall(context)?.resultingDescriptor ?: return + if (!descriptor.returnsFunction()) { + return + } + + val function = descriptor.source.getPsi() as? KtFunction ?: return + if (function.hasBlockBody() || function.bodyExpression !is KtLambdaExpression) { + return + } + + holder.registerProblem(expression, + "Unused return value of a function with lambda expression body", + RemoveEqTokenFromFunctionDeclarationFix(function)) + } + } + } + + private fun KtExpression.used(context: BindingContext): Boolean = context[BindingContext.USED_AS_EXPRESSION, this] ?: true + + private fun CallableDescriptor.returnsFunction() = returnType?.isFunctionType ?: false + + class RemoveEqTokenFromFunctionDeclarationFix(val function: KtFunction) : LocalQuickFix { + override fun getName(): String = "Remove '=' token from function declaration" + + override fun getFamilyName(): String = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + if (!FileModificationService.getInstance().preparePsiElementForWrite(function)) { + return + } + + function.equalsToken?.apply { + // TODO: This should be done by formatter but there is no rule for this now + if (prevSibling.isSpace() && nextSibling.isSpace()) { + prevSibling.delete() + } + delete() + } + } + + private fun PsiElement.isSpace() = text == " " + } +} \ No newline at end of file diff --git a/idea/testData/inspections/unusedLambdaExpressionBody/inspectionData/expected.xml b/idea/testData/inspections/unusedLambdaExpressionBody/inspectionData/expected.xml new file mode 100644 index 00000000000..9bc228be6fc --- /dev/null +++ b/idea/testData/inspections/unusedLambdaExpressionBody/inspectionData/expected.xml @@ -0,0 +1,18 @@ + + + simple.kt + 3 + light_idea_test_case + + Unused return value of a function with lambda expression body + Unused return value of a function with lambda expression body + + + simple.kt + 6 + light_idea_test_case + + Unused return value of a function with lambda expression body + Unused return value of a function with lambda expression body + + \ No newline at end of file diff --git a/idea/testData/inspections/unusedLambdaExpressionBody/inspectionData/inspections.test b/idea/testData/inspections/unusedLambdaExpressionBody/inspectionData/inspections.test new file mode 100644 index 00000000000..ba7b69be858 --- /dev/null +++ b/idea/testData/inspections/unusedLambdaExpressionBody/inspectionData/inspections.test @@ -0,0 +1 @@ +// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.UnusedLambdaExpressionBodyInspection diff --git a/idea/testData/inspections/unusedLambdaExpressionBody/simple.kt b/idea/testData/inspections/unusedLambdaExpressionBody/simple.kt new file mode 100644 index 00000000000..34dc5b3e0f3 --- /dev/null +++ b/idea/testData/inspections/unusedLambdaExpressionBody/simple.kt @@ -0,0 +1,13 @@ + +fun foo() { + bar() + val a = bar() + if (bar() != null) { + bar() + } + bar()() +} + +fun bar() = { + +} \ No newline at end of file diff --git a/idea/testData/quickfix/removeEqTokenFromFunctionDeclaration/.inspection b/idea/testData/quickfix/removeEqTokenFromFunctionDeclaration/.inspection new file mode 100644 index 00000000000..607b45277a6 --- /dev/null +++ b/idea/testData/quickfix/removeEqTokenFromFunctionDeclaration/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.UnusedLambdaExpressionBodyInspection \ No newline at end of file diff --git a/idea/testData/quickfix/removeEqTokenFromFunctionDeclaration/simple.kt b/idea/testData/quickfix/removeEqTokenFromFunctionDeclaration/simple.kt new file mode 100644 index 00000000000..6aa2cd2b044 --- /dev/null +++ b/idea/testData/quickfix/removeEqTokenFromFunctionDeclaration/simple.kt @@ -0,0 +1,9 @@ +// "Remove '=' token from function declaration" "true" + +fun foo() { + bar() +} + +fun bar() = { + +} \ No newline at end of file diff --git a/idea/testData/quickfix/removeEqTokenFromFunctionDeclaration/simple.kt.after b/idea/testData/quickfix/removeEqTokenFromFunctionDeclaration/simple.kt.after new file mode 100644 index 00000000000..0104a42661f --- /dev/null +++ b/idea/testData/quickfix/removeEqTokenFromFunctionDeclaration/simple.kt.after @@ -0,0 +1,9 @@ +// "Remove '=' token from function declaration" "true" + +fun foo() { + bar() +} + +fun bar() { + +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java index e739a605cdb..12596416c53 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java @@ -311,6 +311,12 @@ public class InspectionTestGenerated extends AbstractInspectionTest { doTest(fileName); } + @TestMetadata("unusedLambdaExpressionBody/inspectionData/inspections.test") + public void testUnusedLambdaExpressionBody_inspectionData_Inspections_test() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/unusedLambdaExpressionBody/inspectionData/inspections.test"); + doTest(fileName); + } + @TestMetadata("unusedReceiverParameter/inspectionData/inspections.test") public void testUnusedReceiverParameter_inspectionData_Inspections_test() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/unusedReceiverParameter/inspectionData/inspections.test"); diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 502e567568d..ec4e14f3364 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -7732,6 +7732,21 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { } } + @TestMetadata("idea/testData/quickfix/removeEqTokenFromFunctionDeclaration") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class RemoveEqTokenFromFunctionDeclaration extends AbstractQuickFixTest { + public void testAllFilesPresentInRemoveEqTokenFromFunctionDeclaration() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/removeEqTokenFromFunctionDeclaration"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/removeEqTokenFromFunctionDeclaration/simple.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/quickfix/removeFinalUpperBound") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)