diff --git a/idea/resources/inspectionDescriptions/UnnecessaryVariable.html b/idea/resources/inspectionDescriptions/UnnecessaryVariable.html new file mode 100644 index 00000000000..94431e516e5 --- /dev/null +++ b/idea/resources/inspectionDescriptions/UnnecessaryVariable.html @@ -0,0 +1,6 @@ + + +This inspection reports local variables either used only in the very next return or exact copies of other variables. +In both cases it's better to inline such a variable. + + diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index eb9497c2a40..d91d91dc084 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -2400,6 +2400,15 @@ language="kotlin" /> + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/UnnecessaryVariableInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/UnnecessaryVariableInspection.kt new file mode 100644 index 00000000000..03390562c28 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/UnnecessaryVariableInspection.kt @@ -0,0 +1,95 @@ +/* + * 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.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElementVisitor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.VariableDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.refactoring.inline.KotlinInlineValHandler +import org.jetbrains.kotlin.psi.KtNameReferenceExpression +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtReturnExpression +import org.jetbrains.kotlin.psi.KtVisitorVoid +import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComments +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType +import org.jetbrains.kotlin.resolve.BindingContext.DECLARATION_TO_DESCRIPTOR +import org.jetbrains.kotlin.resolve.BindingContext.REFERENCE_TARGET + +class UnnecessaryVariableInspection : AbstractKotlinInspection() { + + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor = + object : KtVisitorVoid() { + override fun visitProperty(property: KtProperty) { + super.visitProperty(property) + + if (!property.isLocal) return + val initializer = property.initializer ?: return + val nameIdentifier = property.nameIdentifier ?: return + + if (!property.isVar && initializer is KtNameReferenceExpression && property.typeReference == null) { + val context = property.analyze() + val initializerDescriptor = context[REFERENCE_TARGET, initializer] + if (initializerDescriptor is VariableDescriptor) { + if (!initializerDescriptor.isVar && + initializerDescriptor.containingDeclaration is FunctionDescriptor) { + holder.registerProblem( + nameIdentifier, + "Variable is an exact copy of another variable and can be inlined", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + InlineVariableFix() + ) + return + } + } + } + + val nextStatement = property.getNextSiblingIgnoringWhitespaceAndComments() + if (nextStatement is KtReturnExpression) { + val returned = nextStatement.returnedExpression + if (returned is KtNameReferenceExpression) { + val context = nextStatement.analyze() + if (context[REFERENCE_TARGET, returned] == context[DECLARATION_TO_DESCRIPTOR, property]) { + holder.registerProblem( + nameIdentifier, + "Variable used only in following return and can be inlined", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + InlineVariableFix() + ) + } + } + } + } + } + + class InlineVariableFix : LocalQuickFix { + + override fun getName() = "Inline variable" + + override fun getFamilyName() = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val property = descriptor.psiElement.getParentOfType(strict = true) ?: return + KotlinInlineValHandler().inlineElement(project, property.findExistingEditor(), property) + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unnecessaryVariable/.inspection b/idea/testData/inspectionsLocal/unnecessaryVariable/.inspection new file mode 100644 index 00000000000..d93cf0244e1 --- /dev/null +++ b/idea/testData/inspectionsLocal/unnecessaryVariable/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.UnnecessaryVariableInspection \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unnecessaryVariable/copyOfVal.kt b/idea/testData/inspectionsLocal/unnecessaryVariable/copyOfVal.kt new file mode 100644 index 00000000000..cc5c1c6d391 --- /dev/null +++ b/idea/testData/inspectionsLocal/unnecessaryVariable/copyOfVal.kt @@ -0,0 +1,5 @@ +fun test(): Int { + val x = 1 + val y = x + return x + y +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unnecessaryVariable/copyOfVal.kt.after b/idea/testData/inspectionsLocal/unnecessaryVariable/copyOfVal.kt.after new file mode 100644 index 00000000000..40073d8fffd --- /dev/null +++ b/idea/testData/inspectionsLocal/unnecessaryVariable/copyOfVal.kt.after @@ -0,0 +1,4 @@ +fun test(): Int { + val x = 1 + return x + x +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unnecessaryVariable/copyOfValWithExplicitType.kt b/idea/testData/inspectionsLocal/unnecessaryVariable/copyOfValWithExplicitType.kt new file mode 100644 index 00000000000..e6309caa174 --- /dev/null +++ b/idea/testData/inspectionsLocal/unnecessaryVariable/copyOfValWithExplicitType.kt @@ -0,0 +1,8 @@ +// PROBLEM: none + +fun test(): Int { + val x = 1 + // With explicitly given type looks dangerous + val y: Int = x + return x + y +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unnecessaryVariable/copyOfVar.kt b/idea/testData/inspectionsLocal/unnecessaryVariable/copyOfVar.kt new file mode 100644 index 00000000000..a18eb4ee8f7 --- /dev/null +++ b/idea/testData/inspectionsLocal/unnecessaryVariable/copyOfVar.kt @@ -0,0 +1,8 @@ +// PROBLEM: none + +fun test(): Int { + var x = 1 + val y = x + x++ + return x + y +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unnecessaryVariable/paramCopy.kt b/idea/testData/inspectionsLocal/unnecessaryVariable/paramCopy.kt new file mode 100644 index 00000000000..8caec8e0903 --- /dev/null +++ b/idea/testData/inspectionsLocal/unnecessaryVariable/paramCopy.kt @@ -0,0 +1,4 @@ +fun sqr(arg: Int): Int { + val other = arg + return other * other +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unnecessaryVariable/paramCopy.kt.after b/idea/testData/inspectionsLocal/unnecessaryVariable/paramCopy.kt.after new file mode 100644 index 00000000000..f28309dca00 --- /dev/null +++ b/idea/testData/inspectionsLocal/unnecessaryVariable/paramCopy.kt.after @@ -0,0 +1,3 @@ +fun sqr(arg: Int): Int { + return arg * arg +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unnecessaryVariable/propertyCopy.kt b/idea/testData/inspectionsLocal/unnecessaryVariable/propertyCopy.kt new file mode 100644 index 00000000000..744a3dcfbcf --- /dev/null +++ b/idea/testData/inspectionsLocal/unnecessaryVariable/propertyCopy.kt @@ -0,0 +1,9 @@ +// PROBLEM: none + +class My(val x: Number) + +fun My.foo(): Int { + val y = x + if (y is Int) return y + return 0 +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unnecessaryVariable/simpleReturn.kt b/idea/testData/inspectionsLocal/unnecessaryVariable/simpleReturn.kt new file mode 100644 index 00000000000..f8f2b8e8580 --- /dev/null +++ b/idea/testData/inspectionsLocal/unnecessaryVariable/simpleReturn.kt @@ -0,0 +1,4 @@ +fun sum(a: Int, b: Int): Int { + val c = a + b + return c +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unnecessaryVariable/simpleReturn.kt.after b/idea/testData/inspectionsLocal/unnecessaryVariable/simpleReturn.kt.after new file mode 100644 index 00000000000..1516f76e075 --- /dev/null +++ b/idea/testData/inspectionsLocal/unnecessaryVariable/simpleReturn.kt.after @@ -0,0 +1,3 @@ +fun sum(a: Int, b: Int): Int { + return a + b +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unnecessaryVariable/topLevelCopy.kt b/idea/testData/inspectionsLocal/unnecessaryVariable/topLevelCopy.kt new file mode 100644 index 00000000000..fff5b40d8d0 --- /dev/null +++ b/idea/testData/inspectionsLocal/unnecessaryVariable/topLevelCopy.kt @@ -0,0 +1,9 @@ +// PROBLEM: none + +val x: Number? = null + +fun foo(): Int { + val y = x + if (y is Int) return y + return 0 +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unnecessaryVariable/varCopy.kt b/idea/testData/inspectionsLocal/unnecessaryVariable/varCopy.kt new file mode 100644 index 00000000000..fe62349cb3d --- /dev/null +++ b/idea/testData/inspectionsLocal/unnecessaryVariable/varCopy.kt @@ -0,0 +1,7 @@ +// PROBLEM: none + +fun sqrPlusOne(arg: Int): Int { + var other = arg + other++ + return other * other +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 7c877f98e3c..f10157ac78f 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -1458,6 +1458,63 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/unnecessaryVariable") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class UnnecessaryVariable extends AbstractLocalInspectionTest { + public void testAllFilesPresentInUnnecessaryVariable() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/unnecessaryVariable"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("copyOfVal.kt") + public void testCopyOfVal() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/unnecessaryVariable/copyOfVal.kt"); + doTest(fileName); + } + + @TestMetadata("copyOfValWithExplicitType.kt") + public void testCopyOfValWithExplicitType() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/unnecessaryVariable/copyOfValWithExplicitType.kt"); + doTest(fileName); + } + + @TestMetadata("copyOfVar.kt") + public void testCopyOfVar() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/unnecessaryVariable/copyOfVar.kt"); + doTest(fileName); + } + + @TestMetadata("paramCopy.kt") + public void testParamCopy() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/unnecessaryVariable/paramCopy.kt"); + doTest(fileName); + } + + @TestMetadata("propertyCopy.kt") + public void testPropertyCopy() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/unnecessaryVariable/propertyCopy.kt"); + doTest(fileName); + } + + @TestMetadata("simpleReturn.kt") + public void testSimpleReturn() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/unnecessaryVariable/simpleReturn.kt"); + doTest(fileName); + } + + @TestMetadata("topLevelCopy.kt") + public void testTopLevelCopy() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/unnecessaryVariable/topLevelCopy.kt"); + doTest(fileName); + } + + @TestMetadata("varCopy.kt") + public void testVarCopy() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/unnecessaryVariable/varCopy.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/inspectionsLocal/useExpressionBody") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)