diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/DestructureIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/DestructureIntention.kt index b76684e3317..d23a70b2f43 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/DestructureIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/DestructureIntention.kt @@ -48,7 +48,9 @@ class DestructureIntention : SelfTargetingRangeIntention( override fun applyTo(element: KtDeclaration, editor: Editor?) { val forLoop = element.parent as? KtForExpression val functionLiteral = element.parent?.parent as? KtFunctionLiteral - if (forLoop == null && functionLiteral == null && (element !is KtFunctionLiteral || element.hasParameterSpecification())) { + if (forLoop == null && functionLiteral == null && + (element !is KtFunctionLiteral || element.hasParameterSpecification()) && + (element !is KtVariableDeclaration)) { return } val (usagesToRemove, removeSelectorInLoopRange) = collectUsagesToRemove(element, forLoop) ?: return @@ -67,8 +69,9 @@ class DestructureIntention : SelfTargetingRangeIntention( } names.add(name) } + val joinedNames = names.joinToString() if (forLoop != null) { - element.replace(factory.createDestructuringParameterForLoop("(${names.joinToString()})")) + element.replace(factory.createDestructuringParameterForLoop("($joinedNames)")) if (removeSelectorInLoopRange && loopRange is KtDotQualifiedExpression) { loopRange.replace(loopRange.receiverExpression) @@ -78,24 +81,35 @@ class DestructureIntention : SelfTargetingRangeIntention( val lambda = element.parent as KtLambdaExpression SpecifyExplicitLambdaSignatureIntention().applyTo(lambda, editor) lambda.functionLiteral.valueParameters.singleOrNull()?.replace( - factory.createDestructuringParameterForLambda("(${names.joinToString()})") + factory.createDestructuringParameterForLambda("($joinedNames)") ) } else if (functionLiteral != null) { - element.replace(factory.createDestructuringParameterForLambda("(${names.joinToString()})")) + element.replace(factory.createDestructuringParameterForLambda("($joinedNames)")) + } + else if (element is KtVariableDeclaration) { + val modifiersText = element.modifierList?.text ?: "" + val initializerText = element.initializer!!.text + element.replace(factory.createDestructuringDeclaration("$modifiersText val ($joinedNames) = $initializerText")) } } override fun applicabilityRange(element: KtDeclaration): TextRange? { val forLoopIfAny = element.parent as? KtForExpression val functionLiteral = element.parent?.parent as? KtFunctionLiteral - if (forLoopIfAny == null && functionLiteral == null && (element !is KtFunctionLiteral || element.hasParameterSpecification())) { + if (forLoopIfAny == null && functionLiteral == null && + (element !is KtFunctionLiteral || element.hasParameterSpecification()) && + (element !is KtVariableDeclaration)) { return null } val usagesToRemove = collectUsagesToRemove(element, forLoopIfAny) if (usagesToRemove != null && usagesToRemove.first.isNotEmpty()) { - return if (element is KtFunctionLiteral) element.lBrace.textRange else element.textRange + return when (element) { + is KtFunctionLiteral -> element.lBrace.textRange + is KtVariableDeclaration -> element.nameIdentifier?.textRange ?: element.textRange + else -> element.textRange + } } return null } @@ -104,7 +118,7 @@ class DestructureIntention : SelfTargetingRangeIntention( private fun collectUsagesToRemove(declaration: KtDeclaration, forLoop: KtForExpression?): Pair, Boolean>? { val context = declaration.analyzeFullyAndGetResult().bindingContext - val parameterDescriptor = when (declaration) { + val variableDescriptor = when (declaration) { is KtParameter -> context.get(BindingContext.VALUE_PARAMETER, declaration) is KtFunctionLiteral -> @@ -114,11 +128,13 @@ class DestructureIntention : SelfTargetingRangeIntention( else { context.get(BindingContext.FUNCTION, declaration)?.valueParameters?.singleOrNull() } + is KtVariableDeclaration -> + context.get(BindingContext.VARIABLE, declaration) else -> null } ?: return null - val parameterType = parameterDescriptor.type - if (parameterType.isMarkedNullable) return null - val classDescriptor = parameterType.constructor.declarationDescriptor as? ClassDescriptor ?: return null + val variableType = variableDescriptor.type + if (variableType.isMarkedNullable) return null + val classDescriptor = variableType.constructor.declarationDescriptor as? ClassDescriptor ?: return null var otherUsages = false val usagesToRemove : Array @@ -149,12 +165,13 @@ class DestructureIntention : SelfTargetingRangeIntention( val expressionToSearch = forLoop ?: (declaration as? KtFunctionLiteral) ?: (declaration.parent?.parent as? KtFunctionLiteral) + ?: (declaration as? KtVariableDeclaration)?.parent if (expressionToSearch !is KtExpression) return null expressionToSearch.iterateOverDataClassPropertiesUsagesWithIndex( context, - (declaration as? KtParameter)?.nameAsName ?: Name.identifier("it"), + (declaration as? KtNamedDeclaration)?.nameAsName ?: Name.identifier("it"), classDescriptor, { index, usageData -> usagesToRemove[index] += usageData }, { otherUsages = true } diff --git a/idea/testData/intentions/destructuringVariables/.intention b/idea/testData/intentions/destructuringVariables/.intention new file mode 100644 index 00000000000..275f8c5a32b --- /dev/null +++ b/idea/testData/intentions/destructuringVariables/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.intentions.DestructureIntention diff --git a/idea/testData/intentions/destructuringVariables/caret.kt b/idea/testData/intentions/destructuringVariables/caret.kt new file mode 100644 index 00000000000..71768bc5266 --- /dev/null +++ b/idea/testData/intentions/destructuringVariables/caret.kt @@ -0,0 +1,10 @@ +// IS_APPLICABLE: false + +data class XY(val x: Int, val y: Int) + +fun create() = XY(1, 2) + +fun use(): Int { + val xy = create() + return xy.x + xy.y +} \ No newline at end of file diff --git a/idea/testData/intentions/destructuringVariables/changingVar.kt b/idea/testData/intentions/destructuringVariables/changingVar.kt new file mode 100644 index 00000000000..78ce44f1f6c --- /dev/null +++ b/idea/testData/intentions/destructuringVariables/changingVar.kt @@ -0,0 +1,11 @@ +// IS_APPLICABLE: false + +data class XY(val x: Int, val y: Int) + +fun create() = XY(1, 2) + +fun use(): Int { + var xy = create() + xy = create() + return xy.x + xy.y +} \ No newline at end of file diff --git a/idea/testData/intentions/destructuringVariables/noInitializer.kt b/idea/testData/intentions/destructuringVariables/noInitializer.kt new file mode 100644 index 00000000000..a245364fef2 --- /dev/null +++ b/idea/testData/intentions/destructuringVariables/noInitializer.kt @@ -0,0 +1,11 @@ +// IS_APPLICABLE: false + +data class XY(val x: Int, val y: Int) + +fun create() = XY(1, 2) + +fun use(): Int { + val xy: XY + xy = create() + return xy.x + xy.y +} \ No newline at end of file diff --git a/idea/testData/intentions/destructuringVariables/simple.kt b/idea/testData/intentions/destructuringVariables/simple.kt new file mode 100644 index 00000000000..761625fe9c6 --- /dev/null +++ b/idea/testData/intentions/destructuringVariables/simple.kt @@ -0,0 +1,8 @@ +data class XY(val x: Int, val y: Int) + +fun create() = XY(1, 2) + +fun use(): Int { + val xy = create() + return xy.x + xy.y +} \ No newline at end of file diff --git a/idea/testData/intentions/destructuringVariables/simple.kt.after b/idea/testData/intentions/destructuringVariables/simple.kt.after new file mode 100644 index 00000000000..ff71e6422e8 --- /dev/null +++ b/idea/testData/intentions/destructuringVariables/simple.kt.after @@ -0,0 +1,8 @@ +data class XY(val x: Int, val y: Int) + +fun create() = XY(1, 2) + +fun use(): Int { + val (x, y) = create() + return x + y +} \ No newline at end of file diff --git a/idea/testData/intentions/destructuringVariables/var.kt b/idea/testData/intentions/destructuringVariables/var.kt new file mode 100644 index 00000000000..bb627fa3efc --- /dev/null +++ b/idea/testData/intentions/destructuringVariables/var.kt @@ -0,0 +1,8 @@ +data class XY(val x: Int, val y: Int) + +fun create() = XY(1, 2) + +fun use(): Int { + var xy = create() + return xy.x + xy.y +} \ No newline at end of file diff --git a/idea/testData/intentions/destructuringVariables/var.kt.after b/idea/testData/intentions/destructuringVariables/var.kt.after new file mode 100644 index 00000000000..ff71e6422e8 --- /dev/null +++ b/idea/testData/intentions/destructuringVariables/var.kt.after @@ -0,0 +1,8 @@ +data class XY(val x: Int, val y: Int) + +fun create() = XY(1, 2) + +fun use(): Int { + val (x, y) = create() + return x + y +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index fff8baf6fe8..5dd6e6aef8b 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -6319,6 +6319,45 @@ public class IntentionTestGenerated extends AbstractIntentionTest { } } + @TestMetadata("idea/testData/intentions/destructuringVariables") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class DestructuringVariables extends AbstractIntentionTest { + public void testAllFilesPresentInDestructuringVariables() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/destructuringVariables"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("caret.kt") + public void testCaret() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/destructuringVariables/caret.kt"); + doTest(fileName); + } + + @TestMetadata("changingVar.kt") + public void testChangingVar() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/destructuringVariables/changingVar.kt"); + doTest(fileName); + } + + @TestMetadata("noInitializer.kt") + public void testNoInitializer() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/destructuringVariables/noInitializer.kt"); + doTest(fileName); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/destructuringVariables/simple.kt"); + doTest(fileName); + } + + @TestMetadata("var.kt") + public void testVar() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/destructuringVariables/var.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/intentions/ifNullToElvis") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)