diff --git a/idea/resources/META-INF/plugin-common.xml b/idea/resources/META-INF/plugin-common.xml index 83bdc03a458..7ac338af9d6 100644 --- a/idea/resources/META-INF/plugin-common.xml +++ b/idea/resources/META-INF/plugin-common.xml @@ -2932,6 +2932,15 @@ language="kotlin" /> + + + +This inspection reports the variable declaration that can be moved inside when expression. + + \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/MoveVariableDeclarationIntoWhenInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/MoveVariableDeclarationIntoWhenInspection.kt new file mode 100644 index 00000000000..401c09d82dd --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/MoveVariableDeclarationIntoWhenInspection.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.inspections + +import com.intellij.codeInspection.CleanupLocalInspectionTool +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElementVisitor +import com.intellij.psi.SmartPsiElementPointer +import org.jetbrains.kotlin.idea.intentions.loopToCallChain.countUsages +import org.jetbrains.kotlin.idea.intentions.loopToCallChain.previousStatement +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer + +class MoveVariableDeclarationIntoWhenInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor = + whenExpressionVisitor(fun(expression: KtWhenExpression) { + val subjectExpression = expression.subjectExpression ?: return + val property = expression.findDeclarationNear() ?: return + if (!property.isUsedOnlyIn(expression)) return + val identifier = property.nameIdentifier ?: return + holder.registerProblem( + property, + TextRange.from(identifier.startOffsetInParent, identifier.textLength), + "Variable declaration could be moved inside `when`", + MoveVariableDeclarationIntoWhenFix(subjectExpression.createSmartPointer()) + ) + }) +} + +private fun KtProperty.isUsedOnlyIn(element: KtElement): Boolean = countUsages() == countUsages(element) + +private fun KtWhenExpression.findDeclarationNear(): KtProperty? { + val previousProperty = previousStatement() as? KtProperty ?: return null + return previousProperty.takeIf { !it.isVar && it.hasInitializer() && it.nameIdentifier?.text == subjectExpression?.text } +} + +private class MoveVariableDeclarationIntoWhenFix(val subjectExpressionPointer: SmartPsiElementPointer) : LocalQuickFix { + override fun getName() = "Move the variable into `when`" + + override fun getFamilyName() = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val property = descriptor.psiElement as? KtProperty ?: return + val subjectExpression = subjectExpressionPointer.element ?: return + subjectExpression.replace(property.copy()) + property.delete() + } +} diff --git a/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/.inspection b/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/.inspection new file mode 100644 index 00000000000..e36dae938b2 --- /dev/null +++ b/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.MoveVariableDeclarationIntoWhenInspection diff --git a/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/notApplicableOtherName.kt b/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/notApplicableOtherName.kt new file mode 100644 index 00000000000..e255c26cb95 --- /dev/null +++ b/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/notApplicableOtherName.kt @@ -0,0 +1,11 @@ +// PROBLEM: none +fun foo() { + val a = 1 + val b = 1 + when (a) { + 1 -> { + } + else -> { + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/notApplicableSideEffect.kt b/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/notApplicableSideEffect.kt new file mode 100644 index 00000000000..7365329c50e --- /dev/null +++ b/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/notApplicableSideEffect.kt @@ -0,0 +1,11 @@ +// PROBLEM: none +fun foo() { + val a = 1 + val b = 42 + when (a) { + 1 -> { + } + else -> { + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/notApplicableUsedInOtherScope.kt b/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/notApplicableUsedInOtherScope.kt new file mode 100644 index 00000000000..463f71343e4 --- /dev/null +++ b/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/notApplicableUsedInOtherScope.kt @@ -0,0 +1,11 @@ +// PROBLEM: none +fun foo() { + val a = 1 + when (a) { + 1 -> { + } + else -> { + } + } + val b = a +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/notApplicableVar.kt b/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/notApplicableVar.kt new file mode 100644 index 00000000000..5e971ba2a05 --- /dev/null +++ b/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/notApplicableVar.kt @@ -0,0 +1,10 @@ +// PROBLEM: none +fun foo() { + var a = 1 + when (a) { + 1 -> { + } + else -> { + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/simple.kt b/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/simple.kt new file mode 100644 index 00000000000..ba33e61740a --- /dev/null +++ b/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/simple.kt @@ -0,0 +1,9 @@ +fun test() = 42 + +fun foo() { + val a = test() + when (a) { + 1 -> a + else -> 24 + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/simple.kt.after b/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/simple.kt.after new file mode 100644 index 00000000000..8fac77004db --- /dev/null +++ b/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/simple.kt.after @@ -0,0 +1,8 @@ +fun test() = 42 + +fun foo() { + when (val a = test()) { + 1 -> a + else -> 24 + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/withComment.kt b/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/withComment.kt new file mode 100644 index 00000000000..acdf7bee89b --- /dev/null +++ b/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/withComment.kt @@ -0,0 +1,11 @@ +fun foo() { + val a = 1 + + // comment + when (a) { + 1 -> { + } + else -> { + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/withComment.kt.after b/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/withComment.kt.after new file mode 100644 index 00000000000..a2340155488 --- /dev/null +++ b/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/withComment.kt.after @@ -0,0 +1,10 @@ +fun foo() { + + // comment + when (val a = 1) { + 1 -> { + } + else -> { + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/withNewLine.kt b/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/withNewLine.kt new file mode 100644 index 00000000000..f7fe61f33cc --- /dev/null +++ b/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/withNewLine.kt @@ -0,0 +1,11 @@ +fun foo() { + val a = 1 + + + when (a) { + 1 -> { + } + else -> { + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/withNewLine.kt.after b/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/withNewLine.kt.after new file mode 100644 index 00000000000..3c250ade30c --- /dev/null +++ b/idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/withNewLine.kt.after @@ -0,0 +1,10 @@ +fun foo() { + + + when (val a = 1) { + 1 -> { + } + else -> { + } + } +} \ 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 a2143181688..b107a111639 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -4277,6 +4277,54 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class MoveVariableDeclarationIntoWhen extends AbstractLocalInspectionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath); + } + + public void testAllFilesPresentInMoveVariableDeclarationIntoWhen() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); + } + + @TestMetadata("notApplicableOtherName.kt") + public void testNotApplicableOtherName() throws Exception { + runTest("idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/notApplicableOtherName.kt"); + } + + @TestMetadata("notApplicableSideEffect.kt") + public void testNotApplicableSideEffect() throws Exception { + runTest("idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/notApplicableSideEffect.kt"); + } + + @TestMetadata("notApplicableUsedInOtherScope.kt") + public void testNotApplicableUsedInOtherScope() throws Exception { + runTest("idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/notApplicableUsedInOtherScope.kt"); + } + + @TestMetadata("notApplicableVar.kt") + public void testNotApplicableVar() throws Exception { + runTest("idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/notApplicableVar.kt"); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + runTest("idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/simple.kt"); + } + + @TestMetadata("withComment.kt") + public void testWithComment() throws Exception { + runTest("idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/withComment.kt"); + } + + @TestMetadata("withNewLine.kt") + public void testWithNewLine() throws Exception { + runTest("idea/testData/inspectionsLocal/moveVariableDeclarationIntoWhen/withNewLine.kt"); + } + } + @TestMetadata("idea/testData/inspectionsLocal/nestedLambdaShadowedImplicitParameter") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)