diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt index 038d188d3cd..cc1d5bdc72b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt @@ -37,13 +37,19 @@ import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumClass import org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumEntry import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import java.util.* -interface WhenMissingCase +interface WhenMissingCase { + + val branchConditionText: String +} // Always must be first in the list private object UnknownMissingCase : WhenMissingCase { override fun toString() = "unknown" + + override val branchConditionText = "else" } val List.hasUnknown: Boolean @@ -61,7 +67,9 @@ private interface WhenExhaustivenessChecker { } private object NullMissingCase : WhenMissingCase { - override fun toString() = "null" + override fun toString() = branchConditionText + + override val branchConditionText = "null" } private object WhenOnNullableExhaustivenessChecker : WhenExhaustivenessChecker { @@ -88,7 +96,9 @@ private object WhenOnNullableExhaustivenessChecker : WhenExhaustivenessChecker { } private class BooleanMissingCase(val b: Boolean) : WhenMissingCase { - override fun toString() = b.toString() + override fun toString() = branchConditionText + + override val branchConditionText = b.toString() } private object WhenOnBooleanExhaustivenessChecker : WhenExhaustivenessChecker { @@ -121,6 +131,8 @@ private object WhenOnBooleanExhaustivenessChecker : WhenExhaustivenessChecker { private class ClassMissingCase(val descriptor: ClassDescriptor): WhenMissingCase { override fun toString() = descriptor.name.identifier.let { if (descriptor.kind.isSingleton) it else "is $it" } + + override val branchConditionText = descriptor.fqNameSafe.asString().let { if (descriptor.kind.isSingleton) it else "is $it" } } private abstract class WhenOnClassExhaustivenessChecker : WhenExhaustivenessChecker { diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/KotlinBundle.properties b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/KotlinBundle.properties index 978c737a23c..2a1bdf208c5 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/KotlinBundle.properties +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/KotlinBundle.properties @@ -150,6 +150,8 @@ remove.val.var.from.parameter=Remove ''{0}'' from parameter add.override.to.equals.hashCode.toString=Add 'override' to equals, hashCode, toString in project add.when.else.branch.action.family.name=Add else branch add.when.else.branch.action=Add else branch +add.when.remaining.branches.action.family.name=Add remaining branches +add.when.remaining.branches.action=Add remaining branches move.when.else.branch.to.the.end.action=Move else branch to the end move.when.else.branch.to.the.end.family.name=Move else branch to the end change.to.property.name.family.name=Change to property name diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddWhenRemainingBranchesFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddWhenRemainingBranchesFix.kt new file mode 100644 index 00000000000..13eaacebd80 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddWhenRemainingBranchesFix.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2010-2016 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.quickfix + +import com.intellij.codeInsight.CodeInsightUtilCore +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.cfg.WhenChecker +import org.jetbrains.kotlin.cfg.hasUnknown +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtWhenEntry +import org.jetbrains.kotlin.psi.KtWhenExpression + +class AddWhenRemainingBranchesFix(expression: KtWhenExpression) : KotlinQuickFixAction(expression) { + + override fun getFamilyName() = "Add remaining branches" + + override fun getText() = "Add remaining branches" + + override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean { + return super.isAvailable(project, editor, file) && element.closeBrace != null && + !WhenChecker.getNecessaryCases(element, element.analyze()).hasUnknown + } + + override fun invoke(project: Project, editor: Editor?, file: KtFile) { + val necessaryCases = WhenChecker.getNecessaryCases(element, element.analyze()) + + val whenCloseBrace = element.closeBrace + assert(whenCloseBrace != null) { "isAvailable should check if close brace exist" } + val psiFactory = KtPsiFactory(file) + var insertedBranch: PsiElement? = null + + for (case in necessaryCases) { + val entry = psiFactory.createWhenEntry("${case.branchConditionText} -> throw AssertionError(\"\")") + insertedBranch = element.addBefore(entry, whenCloseBrace) + element.addAfter(psiFactory.createNewLine(), insertedBranch) + + } + + if (insertedBranch != null) { + val insertedWhenEntry = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(insertedBranch) as KtWhenEntry + val textRange = insertedWhenEntry.textRange + val indexOfOpenBrace = insertedWhenEntry.text.indexOf('{') + editor!!.caretModel.moveToOffset(textRange.startOffset + indexOfOpenBrace + 1) + } + } + + companion object { + @JvmStatic + fun createFactory(): KotlinSingleIntentionActionFactory { + return object : KotlinSingleIntentionActionFactory() { + public override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction? { + val element = diagnostic.psiElement + val whenExpression = PsiTreeUtil.getParentOfType(element, KtWhenExpression::class.java, false) ?: return null + return AddWhenRemainingBranchesFix(whenExpression) + } + } + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index e602ef90abd..6cf3df9c370 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -189,6 +189,7 @@ class QuickFixRegistrar : QuickFixContributor { ELSE_MISPLACED_IN_WHEN.registerFactory(MoveWhenElseBranchFix.createFactory()) NO_ELSE_IN_WHEN.registerFactory(AddWhenElseBranchFix.createFactory()) + NO_ELSE_IN_WHEN.registerFactory(AddWhenRemainingBranchesFix.createFactory()) BREAK_OR_CONTINUE_IN_WHEN.registerFactory(AddLoopLabelFix) NO_TYPE_ARGUMENTS_ON_RHS.registerFactory(AddStarProjectionsFix.createFactoryForIsExpression()) diff --git a/idea/testData/quickfix/when/addRemainingBranchesBoolean.kt b/idea/testData/quickfix/when/addRemainingBranchesBoolean.kt new file mode 100644 index 00000000000..2b8fdece1b4 --- /dev/null +++ b/idea/testData/quickfix/when/addRemainingBranchesBoolean.kt @@ -0,0 +1,4 @@ +// "Add remaining branches" "true" +fun test(b: Boolean) = when(b) { + false -> 0 +} diff --git a/idea/testData/quickfix/when/addRemainingBranchesBoolean.kt.after b/idea/testData/quickfix/when/addRemainingBranchesBoolean.kt.after new file mode 100644 index 00000000000..774e4658dc9 --- /dev/null +++ b/idea/testData/quickfix/when/addRemainingBranchesBoolean.kt.after @@ -0,0 +1,5 @@ +// "Add remaining branches" "true" +fun test(b: Boolean) = when(b) { + false -> 0 + true -> throw AssertionError("") +} diff --git a/idea/testData/quickfix/when/addRemainingBranchesEnum.kt b/idea/testData/quickfix/when/addRemainingBranchesEnum.kt new file mode 100644 index 00000000000..813c355e766 --- /dev/null +++ b/idea/testData/quickfix/when/addRemainingBranchesEnum.kt @@ -0,0 +1,5 @@ +// "Add remaining branches" "true" +enum class Color { R, G, B } +fun test(c: Color) = when(c) { + Color.B -> 0xff +} diff --git a/idea/testData/quickfix/when/addRemainingBranchesEnum.kt.after b/idea/testData/quickfix/when/addRemainingBranchesEnum.kt.after new file mode 100644 index 00000000000..7e12c8c35f3 --- /dev/null +++ b/idea/testData/quickfix/when/addRemainingBranchesEnum.kt.after @@ -0,0 +1,7 @@ +// "Add remaining branches" "true" +enum class Color { R, G, B } +fun test(c: Color) = when(c) { + Color.B -> 0xff + Color.R -> throw AssertionError("") + Color.G -> throw AssertionError("") +} diff --git a/idea/testData/quickfix/when/addRemainingBranchesSealed.kt b/idea/testData/quickfix/when/addRemainingBranchesSealed.kt new file mode 100644 index 00000000000..042dcf6a0e3 --- /dev/null +++ b/idea/testData/quickfix/when/addRemainingBranchesSealed.kt @@ -0,0 +1,11 @@ +// "Add remaining branches" "true" +sealed class Variant { + object Singleton : Variant() + + class Something(val x: Int) : Variant() + + object Another : Variant() +} +fun test(v: Variant?) = when(v) { + Variant.Singleton -> "s" +} diff --git a/idea/testData/quickfix/when/addRemainingBranchesSealed.kt.after b/idea/testData/quickfix/when/addRemainingBranchesSealed.kt.after new file mode 100644 index 00000000000..910ba32fb5e --- /dev/null +++ b/idea/testData/quickfix/when/addRemainingBranchesSealed.kt.after @@ -0,0 +1,14 @@ +// "Add remaining branches" "true" +sealed class Variant { + object Singleton : Variant() + + class Something(val x: Int) : Variant() + + object Another : Variant() +} +fun test(v: Variant?) = when(v) { + Variant.Singleton -> "s" + is Variant.Something -> throw AssertionError("") + Variant.Another -> throw AssertionError("") + null -> throw AssertionError("") +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 37683af2f5c..7505e6ad889 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -7568,6 +7568,24 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) public static class When extends AbstractQuickFixTest { + @TestMetadata("addRemainingBranchesBoolean.kt") + public void testAddRemainingBranchesBoolean() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/when/addRemainingBranchesBoolean.kt"); + doTest(fileName); + } + + @TestMetadata("addRemainingBranchesEnum.kt") + public void testAddRemainingBranchesEnum() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/when/addRemainingBranchesEnum.kt"); + doTest(fileName); + } + + @TestMetadata("addRemainingBranchesSealed.kt") + public void testAddRemainingBranchesSealed() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/when/addRemainingBranchesSealed.kt"); + doTest(fileName); + } + public void testAllFilesPresentInWhen() throws Exception { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/when"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); }