Added "Add remaining branches with import" quick fix #KT-16033 Fixed

This commit is contained in:
Toshiaki Kameyama
2017-08-25 10:42:42 +03:00
committed by Mikhail Glukhikh
parent 2fa4c28e0f
commit fe0f44da94
11 changed files with 259 additions and 31 deletions
@@ -20,11 +20,11 @@ import com.intellij.openapi.editor.Editor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.conversion.copy.range
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.ImportDescriptorResult
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtPsiFactory
@@ -59,26 +59,33 @@ class ImportAllMembersIntention : SelfTargetingIntention<KtDotQualifiedExpressio
}
override fun applyTo(element: KtDotQualifiedExpression, editor: Editor?) {
val target = target(element)!!
val classFqName = target.importableFqName!!.parent()
ImportInsertHelper.getInstance(element.project).importDescriptor(element.containingKtFile, target, forceAllUnderImport = true)
val qualifiedExpressions = element.containingKtFile.collectDescendantsOfType<KtDotQualifiedExpression> { qualifiedExpression ->
val qualifierName = qualifiedExpression.receiverExpression.getQualifiedElementSelector() as? KtNameReferenceExpression
qualifierName?.getReferencedNameAsName() == classFqName.shortName() && target(qualifiedExpression)?.importableFqName?.parent() == classFqName
}
//TODO: not deep
ShortenReferences.DEFAULT.process(qualifiedExpressions)
element.importReceiverMembers()
}
private fun target(expression: KtDotQualifiedExpression): DeclarationDescriptor? {
val bindingContext = expression.analyze(BodyResolveMode.PARTIAL)
if (bindingContext[BindingContext.QUALIFIER, expression.receiverExpression] !is ClassQualifier) {
return null
companion object {
fun KtDotQualifiedExpression.importReceiverMembers() {
val target = target(this)!!
val classFqName = target.importableFqName!!.parent()
ImportInsertHelper.getInstance(project).importDescriptor(containingKtFile, target, forceAllUnderImport = true)
val qualifiedExpressions = containingKtFile.collectDescendantsOfType<KtDotQualifiedExpression> { qualifiedExpression ->
val qualifierName = qualifiedExpression.receiverExpression.getQualifiedElementSelector() as? KtNameReferenceExpression
qualifierName?.getReferencedNameAsName() == classFqName.shortName() && target(qualifiedExpression)?.importableFqName?.parent() == classFqName
}
//TODO: not deep
ShortenReferences.DEFAULT.process(qualifiedExpressions)
}
private fun target(expression: KtDotQualifiedExpression): DeclarationDescriptor? {
val bindingContext = expression.analyze(BodyResolveMode.PARTIAL)
if (bindingContext[BindingContext.QUALIFIER, expression.receiverExpression] !is ClassQualifier) {
return null
}
val selector = expression.getQualifiedElementSelector() as? KtNameReferenceExpression ?: return null
return selector.mainReference.resolveToDescriptors(bindingContext).firstOrNull()
}
val selector = expression.getQualifiedElementSelector() as? KtNameReferenceExpression ?: return null
return selector.mainReference.resolveToDescriptors(bindingContext).firstOrNull()
}
}
@@ -16,25 +16,29 @@
package org.jetbrains.kotlin.idea.quickfix
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.cfg.*
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.core.quoteIfNeeded
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtWhenExpression
import org.jetbrains.kotlin.idea.intentions.ImportAllMembersIntention
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
class AddWhenRemainingBranchesFix(expression: KtWhenExpression) : KotlinQuickFixAction<KtWhenExpression>(expression) {
class AddWhenRemainingBranchesFix(
expression: KtWhenExpression,
val withImport: Boolean = false
) : KotlinQuickFixAction<KtWhenExpression>(expression) {
override fun getFamilyName() = "Add remaining branches"
override fun getFamilyName() = text
override fun getText() = "Add remaining branches"
override fun getText() = "Add remaining branches" + if (withImport) " with import" else ""
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean {
val element = element ?: return false
@@ -64,12 +68,37 @@ class AddWhenRemainingBranchesFix(expression: KtWhenExpression) : KotlinQuickFix
val entry = psiFactory.createWhenEntry("$branchConditionText -> TODO()")
element.addBefore(entry, whenCloseBrace)
}
if (withImport) {
importAllEntries(element)
}
}
companion object : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction<PsiElement>? {
val whenExpression = diagnostic.psiElement.getNonStrictParentOfType<KtWhenExpression>() ?: return null
return AddWhenRemainingBranchesFix(whenExpression)
private fun importAllEntries(element: KtWhenExpression) {
with (ImportAllMembersIntention) {
element.entries
.map { it.conditions.toList() }
.flatten()
.firstNotNullResult {
(it as? KtWhenConditionWithExpression)?.expression as? KtDotQualifiedExpression
}?.importReceiverMembers()
}
}
companion object : KotlinIntentionActionsFactory() {
private fun KtWhenExpression.hasEnumSubject(): Boolean {
val subject = subjectExpression ?: return false
val descriptor = subject.analyze().getType(subject)?.constructor?.declarationDescriptor ?: return false
return (descriptor as? ClassDescriptor)?.kind == ClassKind.ENUM_CLASS
}
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
val whenExpression = diagnostic.psiElement.getNonStrictParentOfType<KtWhenExpression>() ?: return emptyList()
val actions = mutableListOf(AddWhenRemainingBranchesFix(whenExpression))
if (whenExpression.hasEnumSubject()) {
actions += AddWhenRemainingBranchesFix(whenExpression, withImport = true)
}
return actions
}
}
}
@@ -0,0 +1,12 @@
// "Add remaining branches with import" "true"
// WITH_RUNTIME
enum class Foo {
A, B, C
}
class Test {
fun foo(e: Foo) {
when<caret> (e) {
}
}
}
@@ -0,0 +1,17 @@
import Foo.*
// "Add remaining branches with import" "true"
// WITH_RUNTIME
enum class Foo {
A, B, C
}
class Test {
fun foo(e: Foo) {
when<caret> (e) {
A -> TODO()
B -> TODO()
C -> TODO()
}
}
}
@@ -0,0 +1,15 @@
// "Add remaining branches with import" "true"
// WITH_RUNTIME
import Foo.*
enum class Foo {
A, B, C
}
class Test {
fun foo(e: Foo) {
when<caret> (e) {
A, Foo.B -> TODO()
}
}
}
@@ -0,0 +1,16 @@
// "Add remaining branches with import" "true"
// WITH_RUNTIME
import Foo.*
enum class Foo {
A, B, C
}
class Test {
fun foo(e: Foo) {
when<caret> (e) {
A, B -> TODO()
C -> TODO()
}
}
}
@@ -0,0 +1,25 @@
// "Add remaining branches with import" "true"
// WITH_RUNTIME
import Foo.*
enum class Foo {
A, B, C
}
enum class Bar {
A, B, C
}
class Test {
fun foo(e: Foo) {
when (e) {
A -> TODO()
B -> TODO()
C -> TODO()
}
}
fun bar(e: Bar) {
when<caret> (e) {
}
}
}
@@ -0,0 +1,28 @@
// "Add remaining branches with import" "true"
// WITH_RUNTIME
import Foo.*
enum class Foo {
A, B, C
}
enum class Bar {
A, B, C
}
class Test {
fun foo(e: Foo) {
when (e) {
A -> TODO()
B -> TODO()
C -> TODO()
}
}
fun bar(e: Bar) {
when<caret> (e) {
Bar.A -> TODO()
Bar.B -> TODO()
Bar.C -> TODO()
}
}
}
@@ -0,0 +1,25 @@
// "Add remaining branches with import" "true"
// WITH_RUNTIME
import Foo.*
enum class Foo {
A, B, C
}
enum class Baz {
AA, B, CC
}
class Test {
fun foo(e: Foo) {
when (e) {
A -> TODO()
B -> TODO()
C -> TODO()
}
}
fun baz(e: Baz) {
when<caret> (e) {
}
}
}
@@ -0,0 +1,30 @@
// "Add remaining branches with import" "true"
// WITH_RUNTIME
import Baz.*
import Foo.*
import Foo.B
enum class Foo {
A, B, C
}
enum class Baz {
AA, B, CC
}
class Test {
fun foo(e: Foo) {
when (e) {
A -> TODO()
B -> TODO()
C -> TODO()
}
}
fun baz(e: Baz) {
when<caret> (e) {
AA -> TODO()
Baz.B -> TODO()
CC -> TODO()
}
}
}
@@ -11887,6 +11887,30 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
doTest(fileName);
}
@TestMetadata("addRemainingBranchesEnumImport1.kt")
public void testAddRemainingBranchesEnumImport1() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/when/addRemainingBranchesEnumImport1.kt");
doTest(fileName);
}
@TestMetadata("addRemainingBranchesEnumImport2.kt")
public void testAddRemainingBranchesEnumImport2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/when/addRemainingBranchesEnumImport2.kt");
doTest(fileName);
}
@TestMetadata("addRemainingBranchesEnumImport3.kt")
public void testAddRemainingBranchesEnumImport3() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/when/addRemainingBranchesEnumImport3.kt");
doTest(fileName);
}
@TestMetadata("addRemainingBranchesEnumImport4.kt")
public void testAddRemainingBranchesEnumImport4() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/when/addRemainingBranchesEnumImport4.kt");
doTest(fileName);
}
@TestMetadata("addRemainingBranchesEnumStatement.kt")
public void testAddRemainingBranchesEnumStatement() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/when/addRemainingBranchesEnumStatement.kt");