From aa4bfadfb18ffea22a38b8c6dde06873d98b3304 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Wed, 13 Apr 2016 17:37:17 +0300 Subject: [PATCH] Intentions: Implement "Introduce local variable" intention #KT-11768 Fixed --- ChangeLog.md | 4 ++ .../inspections/IntentionBasedInspection.kt | 7 +-- .../idea/intentions/SelfTargetingIntention.kt | 6 +- .../after.kt.template | 1 + .../before.kt.template | 1 + .../description.html | 5 ++ idea/src/META-INF/plugin.xml | 5 ++ .../intentions/IntroduceVariableIntention.kt | 59 +++++++++++++++++++ .../intentions/introduceVariable/.intention | 1 + .../introduceVariable/afterExpression.kt | 5 ++ .../afterExpression.kt.after | 5 ++ .../introduceVariable/atExpressionEnd.kt | 5 ++ .../atExpressionEnd.kt.after | 5 ++ .../introduceVariable/atExpressionStart.kt | 5 ++ .../atExpressionStart.kt.after | 5 ++ .../introduceVariable/atNestedExpression.kt | 5 ++ .../atNestedExpression.kt.after | 5 ++ .../introduceVariable/beforeExpression.kt | 6 ++ .../intentions/introduceVariable/noType.kt | 6 ++ .../introduceVariable/nothingType.kt | 4 ++ .../intentions/introduceVariable/unitType.kt | 6 ++ ...lassByNestedGroovyQualifier.before.Main.kt | 1 + .../groovyConstructor.before.Main.kt | 1 + .../changeFunctionParameterType4.kt | 1 + .../intentions/IntentionTestGenerated.java | 57 ++++++++++++++++++ 25 files changed, 204 insertions(+), 7 deletions(-) create mode 100644 idea/resources/intentionDescriptions/IntroduceVariableIntention/after.kt.template create mode 100644 idea/resources/intentionDescriptions/IntroduceVariableIntention/before.kt.template create mode 100644 idea/resources/intentionDescriptions/IntroduceVariableIntention/description.html create mode 100644 idea/src/org/jetbrains/kotlin/idea/intentions/IntroduceVariableIntention.kt create mode 100644 idea/testData/intentions/introduceVariable/.intention create mode 100644 idea/testData/intentions/introduceVariable/afterExpression.kt create mode 100644 idea/testData/intentions/introduceVariable/afterExpression.kt.after create mode 100644 idea/testData/intentions/introduceVariable/atExpressionEnd.kt create mode 100644 idea/testData/intentions/introduceVariable/atExpressionEnd.kt.after create mode 100644 idea/testData/intentions/introduceVariable/atExpressionStart.kt create mode 100644 idea/testData/intentions/introduceVariable/atExpressionStart.kt.after create mode 100644 idea/testData/intentions/introduceVariable/atNestedExpression.kt create mode 100644 idea/testData/intentions/introduceVariable/atNestedExpression.kt.after create mode 100644 idea/testData/intentions/introduceVariable/beforeExpression.kt create mode 100644 idea/testData/intentions/introduceVariable/noType.kt create mode 100644 idea/testData/intentions/introduceVariable/nothingType.kt create mode 100644 idea/testData/intentions/introduceVariable/unitType.kt diff --git a/ChangeLog.md b/ChangeLog.md index dd5b3950bfc..ac6b0f3abad 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -11,6 +11,10 @@ ### IDE +New features: + +- [KT-11768](https://youtrack.jetbrains.com/issue/KT-11768) Implement "Introduce local variable" intention + Issues fixed: - [KT-11145](https://youtrack.jetbrains.com/issue/KT-11145) Use progress indicator when searching usages in Introduce Parameter diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/inspections/IntentionBasedInspection.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/inspections/IntentionBasedInspection.kt index 2887f671c7f..9cf45f0f7d5 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/inspections/IntentionBasedInspection.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/inspections/IntentionBasedInspection.kt @@ -32,9 +32,8 @@ import com.intellij.psi.PsiElementVisitor import com.intellij.psi.PsiFile import com.intellij.util.SmartList import org.jetbrains.kotlin.idea.intentions.SelfTargetingRangeIntention -import org.jetbrains.kotlin.psi.KtElement -abstract class IntentionBasedInspection( +abstract class IntentionBasedInspection( val intentions: List>, protected val problemText: String?, protected val elementType: Class @@ -43,7 +42,7 @@ abstract class IntentionBasedInspection( constructor(intention: SelfTargetingRangeIntention, additionalChecker: (TElement) -> Boolean = { true }) : this(listOf(IntentionData(intention, additionalChecker)), null, intention.elementType) - data class IntentionData( + data class IntentionData( val intention: SelfTargetingRangeIntention, val additionalChecker: (TElement) -> Boolean = { true } ) @@ -88,7 +87,7 @@ abstract class IntentionBasedInspection( get() = ProblemHighlightType.GENERIC_ERROR_OR_WARNING /* we implement IntentionAction to provide isAvailable which will be used to hide outdated items and make sure we never call 'invoke' for such item */ - private class IntentionBasedQuickFix( + private class IntentionBasedQuickFix( private val intention: SelfTargetingRangeIntention, private val text: String, private val additionalChecker: (TElement) -> Boolean, diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/intentions/SelfTargetingIntention.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/intentions/SelfTargetingIntention.kt index 66ed1c20d23..4f0ab4491a0 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/intentions/SelfTargetingIntention.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/intentions/SelfTargetingIntention.kt @@ -34,7 +34,7 @@ import org.jetbrains.kotlin.psi.psiUtil.containsInside import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf import java.util.* -abstract class SelfTargetingIntention( +abstract class SelfTargetingIntention( val elementType: Class, private var text: String, private val familyName: String = text @@ -111,7 +111,7 @@ abstract class SelfTargetingIntention( companion object { private val intentionBasedInspections = HashMap>, IntentionBasedInspection<*>?>() - fun findInspection(intentionClass: Class>): IntentionBasedInspection? { + fun findInspection(intentionClass: Class>): IntentionBasedInspection? { if (intentionBasedInspections.containsKey(intentionClass)) { @Suppress("UNCHECKED_CAST") return intentionBasedInspections[intentionClass] as IntentionBasedInspection? @@ -131,7 +131,7 @@ abstract class SelfTargetingIntention( } } -abstract class SelfTargetingRangeIntention( +abstract class SelfTargetingRangeIntention( elementType: Class, text: String, familyName: String = text diff --git a/idea/resources/intentionDescriptions/IntroduceVariableIntention/after.kt.template b/idea/resources/intentionDescriptions/IntroduceVariableIntention/after.kt.template new file mode 100644 index 00000000000..bb59ce5ae1e --- /dev/null +++ b/idea/resources/intentionDescriptions/IntroduceVariableIntention/after.kt.template @@ -0,0 +1 @@ +val i = Integer.parseInt("1") \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/IntroduceVariableIntention/before.kt.template b/idea/resources/intentionDescriptions/IntroduceVariableIntention/before.kt.template new file mode 100644 index 00000000000..0e3bacf1228 --- /dev/null +++ b/idea/resources/intentionDescriptions/IntroduceVariableIntention/before.kt.template @@ -0,0 +1 @@ +Integer.parseInt("1") \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/IntroduceVariableIntention/description.html b/idea/resources/intentionDescriptions/IntroduceVariableIntention/description.html new file mode 100644 index 00000000000..d3d16572f7c --- /dev/null +++ b/idea/resources/intentionDescriptions/IntroduceVariableIntention/description.html @@ -0,0 +1,5 @@ + + +This intention puts a result of the expression into a variable. + + \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 0482789f132..adc55004a48 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -1196,6 +1196,11 @@ Kotlin + + org.jetbrains.kotlin.idea.intentions.IntroduceVariableIntention + Kotlin + + ( + PsiElement::class.java, CodeInsightBundle.message("intention.introduce.variable.text") +) { + private fun getExpressionToProcess(element: PsiElement): KtExpression? { + val startElement = PsiTreeUtil.skipSiblingsBackward(element, PsiWhiteSpace::class.java) ?: element + return startElement.parentsWithSelf + .filterIsInstance() + .takeWhile { it !is KtDeclarationWithBody } + .firstOrNull { + val parent = it.parent + parent is KtBlockExpression || parent is KtDeclarationWithBody && !parent.hasBlockBody() && parent.bodyExpression == it + } + } + + override fun applicabilityRange(element: PsiElement): TextRange? { + val expression = getExpressionToProcess(element) ?: return null + val type = expression.analyze().getType(expression) + if (type == null || type.isUnit() || type.isNothing()) return null + return element.textRange + } + + override fun applyTo(element: PsiElement, editor: Editor?) { + val expression = getExpressionToProcess(element) ?: return + KotlinIntroduceVariableHandler.doRefactoring(element.project, editor, expression, null, null) + } +} \ No newline at end of file diff --git a/idea/testData/intentions/introduceVariable/.intention b/idea/testData/intentions/introduceVariable/.intention new file mode 100644 index 00000000000..aabeb740f6c --- /dev/null +++ b/idea/testData/intentions/introduceVariable/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.intentions.IntroduceVariableIntention \ No newline at end of file diff --git a/idea/testData/intentions/introduceVariable/afterExpression.kt b/idea/testData/intentions/introduceVariable/afterExpression.kt new file mode 100644 index 00000000000..a2c9d1e8875 --- /dev/null +++ b/idea/testData/intentions/introduceVariable/afterExpression.kt @@ -0,0 +1,5 @@ +fun foo(a: Int, b: Int) = a + b + +fun foo() { + foo(1 + 2, 3 * 4) // +} \ No newline at end of file diff --git a/idea/testData/intentions/introduceVariable/afterExpression.kt.after b/idea/testData/intentions/introduceVariable/afterExpression.kt.after new file mode 100644 index 00000000000..b28ebe48140 --- /dev/null +++ b/idea/testData/intentions/introduceVariable/afterExpression.kt.after @@ -0,0 +1,5 @@ +fun foo(a: Int, b: Int) = a + b + +fun foo() { + val foo = foo(1 + 2, 3 * 4) // +} \ No newline at end of file diff --git a/idea/testData/intentions/introduceVariable/atExpressionEnd.kt b/idea/testData/intentions/introduceVariable/atExpressionEnd.kt new file mode 100644 index 00000000000..820f96302ef --- /dev/null +++ b/idea/testData/intentions/introduceVariable/atExpressionEnd.kt @@ -0,0 +1,5 @@ +fun foo(a: Int, b: Int) = a + b + +fun foo() { + foo(1 + 2, 3 * 4) +} \ No newline at end of file diff --git a/idea/testData/intentions/introduceVariable/atExpressionEnd.kt.after b/idea/testData/intentions/introduceVariable/atExpressionEnd.kt.after new file mode 100644 index 00000000000..d8987c0f78e --- /dev/null +++ b/idea/testData/intentions/introduceVariable/atExpressionEnd.kt.after @@ -0,0 +1,5 @@ +fun foo(a: Int, b: Int) = a + b + +fun foo() { + val foo = foo(1 + 2, 3 * 4) +} \ No newline at end of file diff --git a/idea/testData/intentions/introduceVariable/atExpressionStart.kt b/idea/testData/intentions/introduceVariable/atExpressionStart.kt new file mode 100644 index 00000000000..89b18e974f1 --- /dev/null +++ b/idea/testData/intentions/introduceVariable/atExpressionStart.kt @@ -0,0 +1,5 @@ +fun foo(a: Int, b: Int) = a + b + +fun foo() { + foo(1 + 2, 3 * 4) +} \ No newline at end of file diff --git a/idea/testData/intentions/introduceVariable/atExpressionStart.kt.after b/idea/testData/intentions/introduceVariable/atExpressionStart.kt.after new file mode 100644 index 00000000000..d8987c0f78e --- /dev/null +++ b/idea/testData/intentions/introduceVariable/atExpressionStart.kt.after @@ -0,0 +1,5 @@ +fun foo(a: Int, b: Int) = a + b + +fun foo() { + val foo = foo(1 + 2, 3 * 4) +} \ No newline at end of file diff --git a/idea/testData/intentions/introduceVariable/atNestedExpression.kt b/idea/testData/intentions/introduceVariable/atNestedExpression.kt new file mode 100644 index 00000000000..defab9a06b7 --- /dev/null +++ b/idea/testData/intentions/introduceVariable/atNestedExpression.kt @@ -0,0 +1,5 @@ +fun foo(a: Int, b: Int) = a + b + +fun foo() { + foo(1 + 2, 3 * 4) +} \ No newline at end of file diff --git a/idea/testData/intentions/introduceVariable/atNestedExpression.kt.after b/idea/testData/intentions/introduceVariable/atNestedExpression.kt.after new file mode 100644 index 00000000000..d8987c0f78e --- /dev/null +++ b/idea/testData/intentions/introduceVariable/atNestedExpression.kt.after @@ -0,0 +1,5 @@ +fun foo(a: Int, b: Int) = a + b + +fun foo() { + val foo = foo(1 + 2, 3 * 4) +} \ No newline at end of file diff --git a/idea/testData/intentions/introduceVariable/beforeExpression.kt b/idea/testData/intentions/introduceVariable/beforeExpression.kt new file mode 100644 index 00000000000..aa2a5cb46b4 --- /dev/null +++ b/idea/testData/intentions/introduceVariable/beforeExpression.kt @@ -0,0 +1,6 @@ +// IS_APPLICABLE: false +fun foo(a: Int, b: Int) = a + b + +fun foo() { + foo(1 + 2, 3 * 4) +} \ No newline at end of file diff --git a/idea/testData/intentions/introduceVariable/noType.kt b/idea/testData/intentions/introduceVariable/noType.kt new file mode 100644 index 00000000000..1ad2ffd0a90 --- /dev/null +++ b/idea/testData/intentions/introduceVariable/noType.kt @@ -0,0 +1,6 @@ +// IS_APPLICABLE: false +fun foo(a: Int, b: Int) = a + b + +fun foo() { + val foo = foo(1 + 2, 3 * 4) +} \ No newline at end of file diff --git a/idea/testData/intentions/introduceVariable/nothingType.kt b/idea/testData/intentions/introduceVariable/nothingType.kt new file mode 100644 index 00000000000..e2fab45bd4b --- /dev/null +++ b/idea/testData/intentions/introduceVariable/nothingType.kt @@ -0,0 +1,4 @@ +// IS_APPLICABLE: false +fun foo(): Int { + return 1 + 2 +} \ No newline at end of file diff --git a/idea/testData/intentions/introduceVariable/unitType.kt b/idea/testData/intentions/introduceVariable/unitType.kt new file mode 100644 index 00000000000..1b2e6ea1bea --- /dev/null +++ b/idea/testData/intentions/introduceVariable/unitType.kt @@ -0,0 +1,6 @@ +// IS_APPLICABLE: false +fun foo(a: Int, b: Int) {} + +fun foo() { + foo(1 + 2, 3 * 4) +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createClass/referenceExpression/classByNestedGroovyQualifier.before.Main.kt b/idea/testData/quickfix/createFromUsage/createClass/referenceExpression/classByNestedGroovyQualifier.before.Main.kt index a010b632603..2e6fcf20efc 100644 --- a/idea/testData/quickfix/createFromUsage/createClass/referenceExpression/classByNestedGroovyQualifier.before.Main.kt +++ b/idea/testData/quickfix/createFromUsage/createClass/referenceExpression/classByNestedGroovyQualifier.before.Main.kt @@ -1,4 +1,5 @@ // "Create class 'A'" "false" // ACTION: Rename reference +// ACTION: Introduce local variable // ERROR: Unresolved reference: A fun foo() = J.A.B diff --git a/idea/testData/quickfix/createFromUsage/createSecondaryConstructor/groovyConstructor.before.Main.kt b/idea/testData/quickfix/createFromUsage/createSecondaryConstructor/groovyConstructor.before.Main.kt index e74734af81e..69416a5b32f 100644 --- a/idea/testData/quickfix/createFromUsage/createSecondaryConstructor/groovyConstructor.before.Main.kt +++ b/idea/testData/quickfix/createFromUsage/createSecondaryConstructor/groovyConstructor.before.Main.kt @@ -2,5 +2,6 @@ // ERROR: Too many arguments for public constructor G() defined in G // ACTION: Convert to block body // ACTION: Create function 'G' +// ACTION: Introduce local variable fun test() = G(1) \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/parameterTypeMismatch/changeFunctionParameterType4.kt b/idea/testData/quickfix/typeMismatch/parameterTypeMismatch/changeFunctionParameterType4.kt index 2a86e92fd0d..fa3b752e534 100644 --- a/idea/testData/quickfix/typeMismatch/parameterTypeMismatch/changeFunctionParameterType4.kt +++ b/idea/testData/quickfix/typeMismatch/parameterTypeMismatch/changeFunctionParameterType4.kt @@ -1,5 +1,6 @@ // "Change parameter 'z' type of function 'foo' to '(Int) -> String'" "false" // ACTION: To raw string literal +// ACTION: Introduce local variable fun foo(y: Int = 0, z: (Int) -> String = {""}) { foo { diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 49922dca436..b465c89d3fd 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -6103,6 +6103,63 @@ public class IntentionTestGenerated extends AbstractIntentionTest { } } + @TestMetadata("idea/testData/intentions/introduceVariable") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class IntroduceVariable extends AbstractIntentionTest { + @TestMetadata("afterExpression.kt") + public void testAfterExpression() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/introduceVariable/afterExpression.kt"); + doTest(fileName); + } + + public void testAllFilesPresentInIntroduceVariable() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/introduceVariable"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("atExpressionEnd.kt") + public void testAtExpressionEnd() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/introduceVariable/atExpressionEnd.kt"); + doTest(fileName); + } + + @TestMetadata("atExpressionStart.kt") + public void testAtExpressionStart() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/introduceVariable/atExpressionStart.kt"); + doTest(fileName); + } + + @TestMetadata("atNestedExpression.kt") + public void testAtNestedExpression() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/introduceVariable/atNestedExpression.kt"); + doTest(fileName); + } + + @TestMetadata("beforeExpression.kt") + public void testBeforeExpression() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/introduceVariable/beforeExpression.kt"); + doTest(fileName); + } + + @TestMetadata("noType.kt") + public void testNoType() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/introduceVariable/noType.kt"); + doTest(fileName); + } + + @TestMetadata("nothingType.kt") + public void testNothingType() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/introduceVariable/nothingType.kt"); + doTest(fileName); + } + + @TestMetadata("unitType.kt") + public void testUnitType() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/introduceVariable/unitType.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/intentions/invertIfCondition") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)