Use "unnecessary variable" inspection in J2K

Unused variables are no more treated as unnecessary
Related to KT-15958
This commit is contained in:
Mikhail Glukhikh
2017-07-18 16:37:07 +03:00
parent 39f1ef390e
commit 44790eccaf
12 changed files with 91 additions and 58 deletions
@@ -22,14 +22,13 @@ import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElementVisitor import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.refactoring.inline.KotlinInlineValHandler import org.jetbrains.kotlin.idea.refactoring.inline.KotlinInlineValHandler
import org.jetbrains.kotlin.psi.KtNameReferenceExpression import org.jetbrains.kotlin.psi.*
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.getNextSiblingIgnoringWhitespaceAndComments
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.resolve.BindingContext.DECLARATION_TO_DESCRIPTOR import org.jetbrains.kotlin.resolve.BindingContext.DECLARATION_TO_DESCRIPTOR
@@ -42,45 +41,61 @@ class UnnecessaryVariableInspection : AbstractKotlinInspection() {
override fun visitProperty(property: KtProperty) { override fun visitProperty(property: KtProperty) {
super.visitProperty(property) super.visitProperty(property)
if (!property.isLocal) return
val initializer = property.initializer ?: return
val nameIdentifier = property.nameIdentifier ?: return val nameIdentifier = property.nameIdentifier ?: return
val status = statusFor(property) ?: return
holder.registerProblem(
nameIdentifier,
when (status) {
UnnecessaryVariableInspection.Companion.Status.RETURN_ONLY ->
"Variable used only in following return and can be inlined"
UnnecessaryVariableInspection.Companion.Status.EXACT_COPY ->
"Variable is an exact copy of another variable and can be inlined"
},
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
InlineVariableFix()
)
}
}
if (!property.isVar && initializer is KtNameReferenceExpression && property.typeReference == null) { companion object {
val context = property.analyze() private enum class Status {
val initializerDescriptor = context[REFERENCE_TARGET, initializer] RETURN_ONLY,
if (initializerDescriptor is VariableDescriptor) { EXACT_COPY
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() private fun statusFor(property: KtProperty): Status? {
if (nextStatement is KtReturnExpression) { val enclosingElement = KtPsiUtil.getEnclosingElementForLocalDeclaration(property) ?: return null
val returned = nextStatement.returnedExpression val initializer = property.initializer ?: return null
if (returned is KtNameReferenceExpression) {
val context = nextStatement.analyze() if (!property.isVar && initializer is KtNameReferenceExpression && property.typeReference == null) {
if (context[REFERENCE_TARGET, returned] == context[DECLARATION_TO_DESCRIPTOR, property]) { val context = property.analyze()
holder.registerProblem( val initializerDescriptor = context[REFERENCE_TARGET, initializer]
nameIdentifier, if (initializerDescriptor is VariableDescriptor) {
"Variable used only in following return and can be inlined", if (!initializerDescriptor.isVar && initializerDescriptor.containingDeclaration is FunctionDescriptor) {
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, if (ReferencesSearch.search(property, LocalSearchScope(enclosingElement)).findFirst() != null) {
InlineVariableFix() return Status.EXACT_COPY
)
}
} }
} }
} }
} }
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]) {
return Status.RETURN_ONLY
}
}
}
return null
}
fun isActiveFor(property: KtProperty) = statusFor(property) != null
}
class InlineVariableFix : LocalQuickFix { class InlineVariableFix : LocalQuickFix {
override fun getName() = "Inline variable" override fun getName() = "Inline variable"
@@ -26,7 +26,9 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.core.replaced import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.core.setVisibility import org.jetbrains.kotlin.idea.core.setVisibility
import org.jetbrains.kotlin.idea.inspections.RedundantSamConstructorInspection import org.jetbrains.kotlin.idea.inspections.RedundantSamConstructorInspection
import org.jetbrains.kotlin.idea.inspections.UnnecessaryVariableInspection
import org.jetbrains.kotlin.idea.inspections.UseExpressionBodyInspection import org.jetbrains.kotlin.idea.inspections.UseExpressionBodyInspection
import org.jetbrains.kotlin.idea.inspections.findExistingEditor
import org.jetbrains.kotlin.idea.intentions.* import org.jetbrains.kotlin.idea.intentions.*
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.FoldIfToReturnAsymmetricallyIntention import org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.FoldIfToReturnAsymmetricallyIntention
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.FoldIfToReturnIntention import org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.FoldIfToReturnIntention
@@ -37,6 +39,7 @@ import org.jetbrains.kotlin.idea.intentions.conventionNameCalls.ReplaceGetOrSetI
import org.jetbrains.kotlin.idea.intentions.conventionNameCalls.ReplaceGetOrSetIntention import org.jetbrains.kotlin.idea.intentions.conventionNameCalls.ReplaceGetOrSetIntention
import org.jetbrains.kotlin.idea.quickfix.RemoveModifierFix import org.jetbrains.kotlin.idea.quickfix.RemoveModifierFix
import org.jetbrains.kotlin.idea.quickfix.RemoveUselessCastFix import org.jetbrains.kotlin.idea.quickfix.RemoveUselessCastFix
import org.jetbrains.kotlin.idea.refactoring.inline.KotlinInlineValHandler
import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens
@@ -80,6 +83,7 @@ object J2KPostProcessingRegistrar {
_processings.add(RemoveRedundantSamAdaptersProcessing()) _processings.add(RemoveRedundantSamAdaptersProcessing())
_processings.add(RemoveRedundantCastToNullableProcessing()) _processings.add(RemoveRedundantCastToNullableProcessing())
_processings.add(UseExpressionBodyProcessing()) _processings.add(UseExpressionBodyProcessing())
_processings.add(UnnecessaryVariableProcessing())
registerIntentionBasedProcessing(FoldInitializerAndIfToElvisIntention()) registerIntentionBasedProcessing(FoldInitializerAndIfToElvisIntention())
@@ -269,6 +273,18 @@ object J2KPostProcessingRegistrar {
} }
} }
private class UnnecessaryVariableProcessing : J2kPostProcessing {
override fun createAction(element: KtElement, diagnostics: Diagnostics): (() -> Unit)? {
if (element !is KtProperty) return null
if (!UnnecessaryVariableInspection.isActiveFor(element)) return null
return {
KotlinInlineValHandler().inlineElement(element.project, element.findExistingEditor(), element)
}
}
}
private class RemoveRedundantCastToNullableProcessing : J2kPostProcessing { private class RemoveRedundantCastToNullableProcessing : J2kPostProcessing {
override fun createAction(element: KtElement, diagnostics: Diagnostics): (() -> Unit)? { override fun createAction(element: KtElement, diagnostics: Diagnostics): (() -> Unit)? {
if (element !is KtBinaryExpressionWithTypeRHS) return null if (element !is KtBinaryExpressionWithTypeRHS) return null
@@ -0,0 +1,6 @@
// PROBLEM: none
fun test() {
val x = 1
val <caret>y = x
}
@@ -1472,6 +1472,12 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("copyOfValUnused.kt")
public void testCopyOfValUnused() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/unnecessaryVariable/copyOfValUnused.kt");
doTest(fileName);
}
@TestMetadata("copyOfValWithExplicitType.kt") @TestMetadata("copyOfValWithExplicitType.kt")
public void testCopyOfValWithExplicitType() throws Exception { public void testCopyOfValWithExplicitType() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/unnecessaryVariable/copyOfValWithExplicitType.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/unnecessaryVariable/copyOfValWithExplicitType.kt");
@@ -2,8 +2,7 @@ import java.util.HashSet
internal class Foo { internal class Foo {
fun foo(o: HashSet<*>) { fun foo(o: HashSet<*>) {
val o2 = o
var foo = 0 var foo = 0
foo = o2.size foo = o.size
} }
} }
+3 -4
View File
@@ -1,6 +1,5 @@
if (1 > 0) { return if (1 > 0) {
val n = 1 1
return n
} else { } else {
return 0 0
} }
+1 -2
View File
@@ -1,4 +1,3 @@
if (1 > 0) { if (1 > 0) {
val n = 1 return 1
return n
} }
@@ -1,9 +1,8 @@
internal class C { internal class C {
fun foo(o: Any) { fun foo(o: Any) {
if (o is String) { if (o is String) {
val s = o val l = o.length
val l = s.length val substring = o.substring(l - 2)
val substring = s.substring(l - 2)
} }
} }
} }
@@ -8,8 +8,7 @@ internal class Test : Iterable<String> {
} }
fun push(i: Iterator<String>): Iterator<String> { fun push(i: Iterator<String>): Iterator<String> {
val j = i return i
return j
} }
} }
@@ -19,7 +18,6 @@ internal class FullTest : Iterable<String> {
} }
fun push(i: Iterator<String>): Iterator<String> { fun push(i: Iterator<String>): Iterator<String> {
val j = i return i
return j
} }
} }
@@ -10,8 +10,7 @@ internal class Test : Iterable<String> {
} }
fun push(i: Iterator<String>): Iterator<String> { fun push(i: Iterator<String>): Iterator<String> {
val j = i return i
return j
} }
} }
@@ -21,7 +20,6 @@ internal class FullTest : Iterable<String> {
} }
fun push(i: Iterator<String>): Iterator<String> { fun push(i: Iterator<String>): Iterator<String> {
val j = i return i
return j
} }
} }
@@ -7,7 +7,6 @@ internal class Test : Iterable<String> {
} }
fun push(i: Iterator<String>): Iterator<String> { fun push(i: Iterator<String>): Iterator<String> {
val j = i return i
return j
} }
} }
@@ -5,8 +5,7 @@ class C {
try { try {
ByteArrayInputStream(ByteArray(10)).use { stream -> ByteArrayInputStream(ByteArray(10)).use { stream ->
// reading something // reading something
val c = stream.read() return stream.read()
return c
} }
} catch (e: IOException) { } catch (e: IOException) {
println(e) println(e)