Add "unnecessary local variable" inspection #KT-15958 Fixed
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
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.
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -2400,6 +2400,15 @@
|
|||||||
language="kotlin"
|
language="kotlin"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.UnnecessaryVariableInspection"
|
||||||
|
displayName="Unnecessary local variable"
|
||||||
|
groupPath="Kotlin"
|
||||||
|
groupName="Redundant constructs"
|
||||||
|
enabledByDefault="true"
|
||||||
|
level="WEAK WARNING"
|
||||||
|
language="kotlin"
|
||||||
|
/>
|
||||||
|
|
||||||
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
||||||
|
|
||||||
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
||||||
|
|||||||
@@ -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<KtProperty>(strict = true) ?: return
|
||||||
|
KotlinInlineValHandler().inlineElement(project, property.findExistingEditor(), property)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
org.jetbrains.kotlin.idea.inspections.UnnecessaryVariableInspection
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
fun test(): Int {
|
||||||
|
val x = 1
|
||||||
|
val <caret>y = x
|
||||||
|
return x + y
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
fun test(): Int {
|
||||||
|
val x = 1
|
||||||
|
return x + x
|
||||||
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
|
||||||
|
fun test(): Int {
|
||||||
|
val x = 1
|
||||||
|
// With explicitly given type looks dangerous
|
||||||
|
val <caret>y: Int = x
|
||||||
|
return x + y
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
|
||||||
|
fun test(): Int {
|
||||||
|
var x = 1
|
||||||
|
val <caret>y = x
|
||||||
|
x++
|
||||||
|
return x + y
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
fun sqr(arg: Int): Int {
|
||||||
|
val <caret>other = arg
|
||||||
|
return other * other
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fun sqr(arg: Int): Int {
|
||||||
|
return arg * arg
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
|
||||||
|
class My(val x: Number)
|
||||||
|
|
||||||
|
fun My.foo(): Int {
|
||||||
|
val <caret>y = x
|
||||||
|
if (y is Int) return y
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
fun sum(a: Int, b: Int): Int {
|
||||||
|
val <caret>c = a + b
|
||||||
|
return c
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fun sum(a: Int, b: Int): Int {
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
|
||||||
|
val x: Number? = null
|
||||||
|
|
||||||
|
fun foo(): Int {
|
||||||
|
val <caret>y = x
|
||||||
|
if (y is Int) return y
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
|
||||||
|
fun sqrPlusOne(arg: Int): Int {
|
||||||
|
var <caret>other = arg
|
||||||
|
other++
|
||||||
|
return other * other
|
||||||
|
}
|
||||||
@@ -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")
|
@TestMetadata("idea/testData/inspectionsLocal/useExpressionBody")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user